diff --git a/v2/community/reusableMD/custom-ui/frontend-custom-ui-email-password-sign-in.mdx b/v2/community/reusableMD/custom-ui/frontend-custom-ui-email-password-sign-in.mdx new file mode 100644 index 000000000..1acf0c8c7 --- /dev/null +++ b/v2/community/reusableMD/custom-ui/frontend-custom-ui-email-password-sign-in.mdx @@ -0,0 +1,152 @@ + +import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" +import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" +import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" +import WebJsInjector from "/src/components/webJsInjector" +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import AppInfoForm from "/src/components/appInfoForm" + + + + +For the **Sign In** flow you will have to first add the UI elements which will render your form. +After that, call the following function when the user submits the form that you have previously created. + + + + +```tsx +import { signIn } from "supertokens-web-js/recipe/emailpassword"; + +async function signInClicked(email: string, password: string) { + try { + let response = await signIn({ + formFields: [{ + id: "email", + value: email + }, { + id: "password", + value: password + }] + }) + + if (response.status === "FIELD_ERROR") { + response.formFields.forEach(formField => { + if (formField.id === "email") { + // Email validation failed (for example incorrect email syntax). + window.alert(formField.error) + } + }) + } else if (response.status === "WRONG_CREDENTIALS_ERROR") { + window.alert("Email password combination is incorrect.") + } else if (response.status === "SIGN_IN_NOT_ALLOWED") { + // the reason string is a user friendly message + // about what went wrong. It can also contain a support code which users + // can tell you so you know why their sign in was not allowed. + window.alert(response.reason) + } else { + // sign in successful. The session tokens are automatically handled by + // the frontend SDK. + window.location.href = "/homepage" + } + } catch (err: any) { + if (err.isSuperTokensGeneralError === true) { + // this may be a custom error message sent from the API by you. + window.alert(err.message); + } else { + window.alert("Oops! Something went wrong."); + } + } +} +``` + + + + +```tsx +import supertokensEmailPassword from "supertokens-web-js-script/recipe/emailpassword"; +async function signInClicked(email: string, password: string) { + try { + let response = await supertokensEmailPassword.signIn({ + formFields: [{ + id: "email", + value: email + }, { + id: "password", + value: password + }] + }) + + if (response.status === "FIELD_ERROR") { + // one of the input formFields failed validation + response.formFields.forEach(formField => { + if (formField.id === "email") { + // Email validation failed (for example incorrect email syntax). + window.alert(formField.error) + } + }) + } else if (response.status === "WRONG_CREDENTIALS_ERROR") { + window.alert("Email password combination is incorrect.") + } else if (response.status === "SIGN_IN_NOT_ALLOWED") { + // the reason string is a user friendly message + // about what went wrong. It can also contain a support code which users + // can tell you so you know why their sign in was not allowed. + window.alert(response.reason) + } else { + // sign in successful. The session tokens are automatically handled by + // the frontend SDK. + window.location.href = "/homepage" + } + } catch (err: any) { + if (err.isSuperTokensGeneralError === true) { + // this may be a custom error message sent from the API by you. + window.alert(err.message); + } else { + window.alert("Oops! Something went wrong."); + } + } +} +``` + + + + + + + + + + + +For the **Sign In** flow you will have to first add the UI elements which will render your form. +After that, call the following API when the user submits the form that you have previously created. + +```bash +curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signin' \ +--header 'Content-Type: application/json; charset=utf-8' \ +--data-raw '{ + "formFields": [{ + "id": "email", + "value": "john@example.com" + }, { + "id": "password", + "value": "somePassword123" + }] +}' +``` + + + +The response body from the API call has a `status` property in it: +- `status: "OK"`: User sign in was successful. The response also contains more information about the user, for example their user ID. +- `status: "WRONG_CREDENTIALS_ERROR"`: The input email and password combination is incorrect. +- `status: "FIELD_ERROR"`: This indicates that the input email did not pass the backend validation - probably because it's syntactically not an email. You want to show the user an error next to the email input form field. +- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. +- `status: "SIGN_IN_NOT_ALLOWED"`: This can happen during automatic account linking or during MFA. The `reason` prop that's in the response body contains a support code using which you can see why the sign in was not allowed. + + + + + +On success, the backend will send back session tokens as part of the response headers which will be automatically handled by our frontend SDK for you. diff --git a/v2/community/reusableMD/custom-ui/frontend-custom-ui-email-password-sign-up.mdx b/v2/community/reusableMD/custom-ui/frontend-custom-ui-email-password-sign-up.mdx new file mode 100644 index 000000000..d0e024653 --- /dev/null +++ b/v2/community/reusableMD/custom-ui/frontend-custom-ui-email-password-sign-up.mdx @@ -0,0 +1,262 @@ + +import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" +import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" +import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" +import WebJsInjector from "/src/components/webJsInjector" +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import AppInfoForm from "/src/components/appInfoForm" + + + + + +For the **Sign Up** flow you will have to first add the UI elements which will render your form. +After that, call the following function when the user submits the form that you have previously created. + + + + +```tsx +import { signUp } from "supertokens-web-js/recipe/emailpassword"; + +async function signUpClicked(email: string, password: string) { + try { + let response = await signUp({ + formFields: [{ + id: "email", + value: email + }, { + id: "password", + value: password + }] + }) + + if (response.status === "FIELD_ERROR") { + // one of the input formFields failed validation + response.formFields.forEach(formField => { + if (formField.id === "email") { + // Email validation failed (for example incorrect email syntax), + // or the email is not unique. + window.alert(formField.error) + } else if (formField.id === "password") { + // Password validation failed. + // Maybe it didn't match the password strength + window.alert(formField.error) + } + }) + } else if (response.status === "SIGN_UP_NOT_ALLOWED") { + // the reason string is a user friendly message + // about what went wrong. It can also contain a support code which users + // can tell you so you know why their sign up was not allowed. + window.alert(response.reason) + } else { + // sign up successful. The session tokens are automatically handled by + // the frontend SDK. + window.location.href = "/homepage" + } + } catch (err: any) { + if (err.isSuperTokensGeneralError === true) { + // this may be a custom error message sent from the API by you. + window.alert(err.message); + } else { + window.alert("Oops! Something went wrong."); + } + } +} +``` + + + + +```tsx +import supertokensEmailPassword from "supertokens-web-js-script/recipe/emailpassword"; +async function signUpClicked(email: string, password: string) { + try { + let response = await supertokensEmailPassword.signUp({ + formFields: [{ + id: "email", + value: email + }, { + id: "password", + value: password + }] + }) + + if (response.status === "FIELD_ERROR") { + // one of the input formFields failed validation + response.formFields.forEach(formField => { + if (formField.id === "email") { + // Email validation failed (for example incorrect email syntax), + // or the email is not unique. + window.alert(formField.error) + } else if (formField.id === "password") { + // Password validation failed. + // Maybe it didn't match the password strength + window.alert(formField.error) + } + }) + } else if (response.status === "SIGN_UP_NOT_ALLOWED") { + // the reason string is a user friendly message + // about what went wrong. It can also contain a support code which users + // can tell you so you know why their sign in was not allowed. + window.alert(response.reason) + } else { + // sign up successful. The session tokens are automatically handled by + // the frontend SDK. + window.location.href = "/homepage" + } + } catch (err: any) { + if (err.isSuperTokensGeneralError === true) { + // this may be a custom error message sent from the API by you. + window.alert(err.message); + } else { + window.alert("Oops! Something went wrong."); + } + } +} +``` + + + + + + + + + + +For the **Sign Up** flow you will have to first add the UI elements which will render your form. +After that, call the follwing API when the user submits the form that you have previously created. + +```bash +curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signup' \ +--header 'Content-Type: application/json; charset=utf-8' \ +--data-raw '{ + "formFields": [{ + "id": "email", + "value": "john@example.com" + }, { + "id": "password", + "value": "somePassword123" + }] +}' +``` + + + +The response body from the API call has a `status` property in it: +- `status: "OK"`: User creation was successful. The response also contains more information about the user, for example their user ID. +- `status: "FIELD_ERROR"`: One of the form field inputs failed validation. The response body contains information about which form field input based on the `id`: + - The email could fail validation if it's syntactically not an email, of it it's not unique. + - The password could fail validation if it's not string enough (as defined by the backend password validator). + + Either way, you want to show the user an error next to the input form field. +- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. +- `status: "SIGN_UP_NOT_ALLOWED"`: This can happen during automatic account linking or during MFA. The `reason` prop that's in the response body contains a support code using which you can see why the sign up was not allowed. + + + + + +The `formFields` input is a key-value array. You must provide it an `email` and a `password` value at a minimum. If you want to provide additional items, for example the user's name or age, you can append it to the array like so: + +```json +{ + "formFields": [{ + "id": "email", + "value": "john@example.com" + }, { + "id": "password", + "value": "somePassword123" + }, { + "id": "name", + "value": "John Doe" + }] +} +``` + +On the backend, the `formFields` array will be available to you for consumption. + +On success, the backend will send back session tokens as part of the response headers which will be automatically handled by our frontend SDK for you. + +#### How to check if an email is unique + +As a part of the sign up form, you may want to explicitly check that the entered email is unique. Whilst this is already done via the sign up API call, it may be a better UX to warn the user about a non unique email right after they finish typing it. + + + + + + + +```tsx +import { doesEmailExist } from "supertokens-web-js/recipe/emailpassword"; + +async function checkEmail(email: string) { + try { + let response = await doesEmailExist({ + email + }); + + if (response.doesExist) { + window.alert("Email already exists. Please sign in instead") + } + } catch (err: any) { + if (err.isSuperTokensGeneralError === true) { + // this may be a custom error message sent from the API by you. + window.alert(err.message); + } else { + window.alert("Oops! Something went wrong."); + } + } +} +``` + + + + +```tsx +import supertokensEmailPassword from "supertokens-web-js-script/recipe/emailpassword"; +async function checkEmail(email: string) { + try { + let response = await supertokensEmailPassword.doesEmailExist({ + email + }); + + if (response.doesExist) { + window.alert("Email already exists. Please sign in instead") + } + } catch (err: any) { + if (err.isSuperTokensGeneralError === true) { + // this may be a custom error message sent from the API by you. + window.alert(err.message); + } else { + window.alert("Oops! Something went wrong."); + } + } +} +``` + + + + + + + + + + +```bash +curl --location --request GET '^{form_apiDomain}^{form_apiBasePath}/emailpassword/email/exists?email=john@example.com' +``` + + + +The response body from the API call has a `status` property in it: +- `status: "OK"`: The response will also contain a `exists` boolean which will be `true` if the input email already belongs to an email password user. +- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. + + + + diff --git a/v2/community/reusableMD/custom-ui/frontend-custom-ui-mobile-access-token-flow.mdx b/v2/community/reusableMD/custom-ui/frontend-custom-ui-mobile-access-token-flow.mdx new file mode 100644 index 000000000..530a2cc9b --- /dev/null +++ b/v2/community/reusableMD/custom-ui/frontend-custom-ui-mobile-access-token-flow.mdx @@ -0,0 +1,50 @@ + + +import AppInfoForm from "/src/components/appInfoForm" + + +### Fetching the OAuth/Access tokens on the frontend + +1. Sign in with the social provider. The minimum required scope is the one that provides access to the user's email. You can use any library to sign in with the social provider. +2. Get the access token on the frontend if it is available. +3. Get the id token from the sign in result if it is available. + +:::important +You need to provide either the access token or the id token, or both in step 2, depending on what is available. +::: + +### Calling the signinup API to use the OAuth tokens + + + +Once you have the `access_token` or the `id_token` from the auth provider, you need to call the signinup API exposed by our backend SDK as shown below: + +```bash +curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup' \ +--header 'Content-Type: application/json; charset=utf-8' \ +--data-raw '{ + "thirdPartyId": "google", + "clientType": "...", + "oAuthTokens": { + "access_token": "...", + "id_token": "..." + }, +}' +``` + +:::important +- The `clientType` input is optional, and is required only if you have initialised more than one client in the provide on the backend (See the "Social / SSO login for both, web and mobile apps" section below). +- If you have the `id_token`, you can send that along with the `access_token`. +::: + +The response body from the API call has a `status` property in it: +- `status: "OK"`: User sign in / up was successful. The response also contains more information about the user, for example their user ID, and if it was a new user or existing user. +- `status: "NO_EMAIL_GIVEN_BY_PROVIDER"`: This is returned if the social / SSO provider did not provider an email for the user. In this case, you want to ask the user to pick another method of sign in. Or, you can also override the backend functions to create a fake email for the user for this provider. +- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. +- `status: "SIGN_IN_UP_NOT_ALLOWED"`: This can happen during automatic account linking or during MFA. The `reason` prop that's in the response body contains a support code using which you can see why the sign in / up was not allowed. + +:::note +On success, the backend will send back session tokens as part of the response headers which will be automatically handled by our frontend SDK for you. +::: + + diff --git a/v2/community/reusableMD/custom-ui/frontend-custom-ui-mobile-authorization-code-flow.mdx b/v2/community/reusableMD/custom-ui/frontend-custom-ui-mobile-authorization-code-flow.mdx new file mode 100644 index 000000000..5dcfc6528 --- /dev/null +++ b/v2/community/reusableMD/custom-ui/frontend-custom-ui-mobile-authorization-code-flow.mdx @@ -0,0 +1,619 @@ + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import AppInfoForm from "/src/components/appInfoForm" +import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" + +### Sign in with Apple example + + + + + +#### Fetching the authorisation token on the frontend + +For react native apps, this involves setting up the [react-native-apple-authentication library](https://github.com/invertase/react-native-apple-authentication) in your app. Checkout their `README` for steps on how to integrate their SDK into your application. The minimum scope required by SuperTokens is the one that gives the user's email (In case of Apple, that could be the user's actual email or the proxy email provided by Apple - it doesn't really matter). + +Once the integration is done, you should call the `appleAuth.performRequest` function for iOS and the `appleAuthAndroid.signIn` function for Android. Either way, the result of the function will be a one time use auth code which you should send to your backend as shown in the next step. + +A full example of this can be found in [our example app](https://github.com/supertokens/supertokens-react-native/blob/master/examples/with-thirdparty/apple.ts). + +In case you are using Expo, you can use the [expo-apple-authentication](https://docs.expo.dev/versions/latest/sdk/apple-authentication/) library instead (not that this library only works on iOS). + + + + + +#### Fetching the authorisation token on the frontend + +:::info +Coming Soon +::: + + + + + +#### Fetching the authorisation token on the frontend + +For iOS you use the normal sign in with apple flow and then use the authorization code to login with SuperTokens. You can see a full example of this in the `onAppleClicked` function in [our example app](https://github.com/supertokens/supertokens-ios/blob/master/examples/with-thirdparty/with-thirdparty/LoginScreen/LoginScreenViewController.swift). + +```swift +import UIKit +import AuthenticationServices + +fileprivate class ViewController: UIViewController, ASAuthorizationControllerPresentationContextProviding, ASAuthorizationControllerDelegate { + func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor { + return view.window! + } + + func loginWithApple() { + let authorizationRequest = ASAuthorizationAppleIDProvider().createRequest() + authorizationRequest.requestedScopes = [.email, .fullName] + + let authorizationController = ASAuthorizationController(authorizationRequests: [authorizationRequest]) + + authorizationController.presentationContextProvider = self + authorizationController.delegate = self + authorizationController.performRequests() + } + + func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { + guard let credential: ASAuthorizationAppleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential, + let authorizationCode = credential.authorizationCode, + let authorizationCodeString: String = String(data: authorizationCode, encoding: .utf8), + let email: String = credential.email as? String, + let nameComponents: PersonNameComponents = credential.fullName as? PersonNameComponents, + let firstName: String = nameComponents.givenName as? String, + let lastName: String = nameComponents.familyName as? String else {return} + + // Send the user information and auth code to the backend. Refer to the next step. + } +} +``` + + + + + +#### Fetching the authorisation token on the frontend + +For flutter we use the [sign_in_with_apple](https://pub.dev/packages/sign_in_with_apple) package, make sure to follow the prerequisites steps to get the package setup. After setup use the snippet below to trigger the apple sign in flow. You can see a full example of this in the `loginWithApple` function in [our example app](https://github.com/supertokens/supertokens-flutter/blob/master/examples/with-thirdparty/lib/screens/login.dart). + +```dart +import 'package:sign_in_with_apple/sign_in_with_apple.dart'; + +void loginWithApple() async { + try { + var credential = await SignInWithApple.getAppleIDCredential( + scopes: [ + AppleIDAuthorizationScopes.email, + AppleIDAuthorizationScopes.fullName, + ], + // Required for Android only + webAuthenticationOptions: WebAuthenticationOptions( + clientId: "", + redirectUri: Uri.parse( + "//callback/apple", + ), + ), + ); + + String authorizationCode = credential.authorizationCode; + String? idToken = credential.identityToken; + String? email = credential.email; + String? firstname = credential.givenName; + String? lastName = credential.familyName; + + // Send the user information and auth code to the backend. Refer to the next step. + } catch (e) { + // Sign in aborted or failed + } +} +``` + +In the snippet above for Android we need to pass an additional `webAuthenticationOptions` property when signing in with Apple. This is because on Android the library uses the web login flow and we need to provide it with the client id and redirection uri. The `redirectUri` property here is the URL to which Apple will make a `POST` request after the user has logged in. The SuperTokens backend SDKs provide an API for this at `//callback/apple`. + +#### Additional steps for Android + +For android we also need to provide a way for the web login flow to redirect back to the app. By default the API provided by the backend SDKs redirect to the website domain you provide when initialising the SDK, we can override the API to have it redirect to our app instead. For example if you were using the Node.js SDK: + + + + + +```tsx +import ThirdParty from "supertokens-node/recipe/thirdparty"; + +ThirdParty.init({ + // highlight-start + override: { + apis: (original) => { + return { + ...original, + appleRedirectHandlerPOST: async (input) => { + if (original.appleRedirectHandlerPOST === undefined) { + throw Error("Should never come here"); + } + + // inut.formPostInfoFromProvider contains all the query params attached by Apple + const stateInBase64 = input.formPostInfoFromProvider.state; + + // The web SDKs add a default state + if (stateInBase64 === undefined) { + // Redirect to android app + // We create a dummy URL to create the query string + const dummyUrl = new URL("http://localhost:8080"); + for (const [key, value] of Object.entries(input.formPostInfoFromProvider)) { + dummyUrl.searchParams.set(key, `${value}`); + } + + const queryString = dummyUrl.searchParams.toString(); + // Refer to the README of sign_in_with_apple to understand what this url is + const redirectUrl = `intent://callback?${queryString}#Intent;package=YOUR.PACKAGE.IDENTIFIER;scheme=signinwithapple;end`; + + input.options.res.setHeader("Location", redirectUrl, false); + input.options.res.setStatusCode(303); + input.options.res.sendHTMLResponse(""); + } else { + // For the web flow we can use the original implementation + original.appleRedirectHandlerPOST(input); + } + }, + }; + }, + }, + // highlight-end +}) +``` + + + + + +```go +import ( + "net/http" + "strings" + + "github.com/supertokens/supertokens-golang/recipe/thirdparty" + "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" +) + +func main() { + thirdparty.Init(&tpmodels.TypeInput{ + Override: &tpmodels.OverrideStruct{ + // highlight-start + APIs: func(originalImplementation tpmodels.APIInterface) tpmodels.APIInterface { + originalAppleRedirectPost := *originalImplementation.AppleRedirectHandlerPOST + + *originalImplementation.AppleRedirectHandlerPOST = func(formPostInfoFromProvider map[string]interface{}, options tpmodels.APIOptions, userContext *map[string]interface{}) error { + // formPostInfoFromProvider contains all the query params attached by Apple + state, stateOk := formPostInfoFromProvider["state"].(string) + + queryParams := []string{} + if (!stateOk) || state == "" { + // Redirect to android app + for key, value := range formPostInfoFromProvider { + queryParams = append(queryParams, key+"="+value.(string)) + } + + queryString := "" + if len(queryParams) > 0 { + queryString = strings.Join(queryParams, "&") + } + + // Refer to the README of sign_in_with_apple to understand what this url is + redirectUri := "intent://callback?" + queryString + "#Intent;package=YOUR.PACKAGE.IDENTIFIER;scheme=signinwithapple;end" + + options.Res.Header().Set("Location", redirectUri) + options.Res.WriteHeader(http.StatusSeeOther) + return nil + } else { + return originalAppleRedirectPost(formPostInfoFromProvider, options, userContext) + } + } + + return originalImplementation + }, + }, + // highlight-end + }) +} +``` + + + + + +```python +from supertokens_python.recipe import thirdparty +from supertokens_python.recipe.thirdparty.interfaces import APIInterface, APIOptions +from typing import Dict, Any + +# highlight-start +def override_thirdparty_apis(original_implementation: APIInterface): + original_apple_redirect_post = original_implementation.apple_redirect_handler_post + + async def apple_redirect_handler_post( + form_post_info: Dict[str, Any], + api_options: APIOptions, + user_context: Dict[str, Any] + ): + # form_post_info contains all the query params attached by Apple + state = form_post_info["state"] + + # The web SDKs add a default state + if state is None: + query_items = [ + f"{key}={value}" for key, value in form_post_info.items() + ] + + query_string = "&".join(query_items) + + # Refer to the README of sign_in_with_apple to understand what this url is + redirect_url = "intent://callback?" + query_string + "#Intent;package=YOUR.PACKAGE.IDENTIFIER;scheme=signinwithapple;end" + + api_options.response.set_header("Location", redirect_url) + api_options.response.set_status_code(303) + api_options.response.set_html_content("") + else: + return await original_apple_redirect_post(form_post_info, api_options, user_context) + + original_implementation.apple_redirect_handler_post = apple_redirect_handler_post + return original_implementation +# highlight-end + +thirdparty.init( + # highlight-start + override=thirdparty.InputOverrideConfig( + apis=override_thirdparty_apis + ), + # highlight-end +) +``` + + + + + +In the code above we override the `appleRedirectHandlerPOST` API to check if the request was made by our Android app (You can skip checking the state if you only have a mobile app and no website). `sign_in_with_apple` requires us to parse the query params sent by apple and include them in the redirect URL in a specific way, and then we simply redirect to the deep link url. Refer to the README for `sign_in_with_apple` to read about the deep link setup required in Android. + + + + + +#### Calling the signinup API to consume the authorisation token + + + +Once you have the authorisation code from the auth provider, you need to call the signinup API exposed by our backend SDK as shown below: + +```bash +curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup' \ +--header 'Content-Type: application/json; charset=utf-8' \ +--data-raw '{ + "thirdPartyId": "apple", + "clientType": "...", + "redirectURIInfo": { + "redirectURIOnProviderDashboard": "^{form_apiDomain}^{form_apiBasePath}/callback/apple", + "redirectURIQueryParams": { + "code": "...", + "user": { + "name":{ + "firstName":"...", + "lastName":"..." + }, + "email":"..." + } + } + } +}' +``` + +:::important +- On iOS, the client id set in the backend should be the same as the bundle identifier for your app. +- The `clientType` input is optional, and is required only if you have initialised more than one client in the provide on the backend (See the "Social / SSO login for both, web and mobile apps" section below). +- On iOS, `redirectURIOnProviderDashboard` doesn't matter and its value can be a universal link configured for your app. +- On Android, the `redirectURIOnProviderDashboard` should match the one configured on the Apple developer dashboard. +- The `user` object contains information provided by Apple. +::: + +The response body from the API call has a `status` property in it: +- `status: "OK"`: User sign in / up was successful. The response also contains more information about the user, for example their user ID, and if it was a new user or existing user. +- `status: "NO_EMAIL_GIVEN_BY_PROVIDER"`: This is returned if the social / SSO provider did not provider an email for the user. In this case, you want to ask the user to pick another method of sign in. Or, you can also override the backend functions to create a fake email for the user for this provider. +- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. +- `status: "SIGN_IN_UP_NOT_ALLOWED"`: This can happen during automatic account linking or during MFA. The `reason` prop that's in the response body contains a support code using which you can see why the sign in / up was not allowed. + +:::note +On success, the backend will send back session tokens as part of the response headers which will be automatically handled by our frontend SDK for you. +::: + + + +### Sign in with Google Example + + + + + +#### Fetching the authorisation token on the frontend + +This involves setting up the [@react-native-google-signin/google-signin](https://github.com/react-native-google-signin/google-signin) in your app. Checkout their `README` for steps on how to integrate their SDK into your application. The minimum scope required by SuperTokens is the one that gives the user's email. + +Once the library is set up, use `GoogleSignin.configure` and `GoogleSignin.signIn` to trigger the login flow and sign the user in with Google. Refer to [our example app](https://github.com/supertokens/supertokens-react-native/blob/master/examples/with-thirdparty/google.ts) to see the full code for this. + +```tsx +import { GoogleSignin } from "@react-native-google-signin/google-signin"; + +export const performGoogleSignIn = async (): Promise => { + GoogleSignin.configure({ + webClientId: "GOOGLE_WEB_CLIENT_ID", + iosClientId: "GOOGLE_IOS_CLIENT_ID", + }); + + try { + const user = await GoogleSignin.signIn({}); + const authCode = user.serverAuthCode; + + // Refer to step 2 + + return true; + } catch (e) { + console.log("Google sign in failed with error", e); + } + + return false; +}; +``` + + + + + +#### Fetching the authorisation token on the frontend + +Follow the [official Google Sign In guide](https://developers.google.com/identity/sign-in/android/start-integrating) to set up their library and sign the user in with Google. Fetch the authorization code from the google sign in result. For a full example refer to the `signInWithGoogle` function in [our example app](https://github.com/supertokens/supertokens-android/blob/master/examples/with-thirdparty/app/src/main/java/com/supertokens/supertokensexample/LoginActivity.kt). + +```kotlin +import androidx.activity.result.ActivityResult +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import android.util.Log +import com.google.android.gms.auth.api.signin.GoogleSignIn +import com.google.android.gms.auth.api.signin.GoogleSignInOptions +import android.content.Intent + +class LoginActivity : AppCompatActivity() { + private lateinit var googleResultLauncher: ActivityResultLauncher + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + googleResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + onGoogleResultReceived(it) + } + } + + private fun signInWithGoogle() { + val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) + .requestServerAuthCode("GOOGLE_WEB_CLIENT_ID") + .requestEmail() + .build() + + val googleClient = GoogleSignIn.getClient(this, gso) + val signInIntent = googleClient.signInIntent + + googleResultLauncher.launch(signInIntent) + } + + private fun onGoogleResultReceived(it: ActivityResult) { + val task = GoogleSignIn.getSignedInAccountFromIntent(it.data) + val account = task.result + val authCode = account.serverAuthCode + + // Refer to step 2 + } +} +``` + + + + + +#### Fetching the authorisation token on the frontend + +For iOS we use the `GoogleSignIn` library, follow the [official guide](https://developers.google.com/identity/sign-in/ios/start-integrating) to set up the library and sign the user in with Google. Use the result of google sign in to get the authorization code. For a full example refer to the `onGoogleCliked` function in [our example app](https://github.com/supertokens/supertokens-ios/blob/master/examples/with-thirdparty/with-thirdparty/LoginScreen/LoginScreenViewController.swift). + +```swift +import UIKit +import GoogleSignIn + +fileprivate class LoginScreenViewController: UIViewController { + @IBAction func onGoogleCliked() { + GIDSignIn.sharedInstance.signIn(withPresenting: self) { signInResult, error in + guard error == nil else { return } + + guard let authCode: String = signInResult?.serverAuthCode as? String else { + print("Google login did not return an authorisation code") + return + } + + // Refer to step 2 + } + } +} +``` + + + + + +#### Fetching the authorisation token on the frontend + +For flutter we use the [google_sign_in](https://pub.dev/packages/google_sign_in) package, make sure to follow the prerequisites steps to get the package setup. After setup use the snippet below to trigger the google sign in flow. For a full example refer to the `loginWithGoogle` in [our example app](https://github.com/supertokens/supertokens-flutter/blob/master/examples/with-thirdparty/lib/screens/login.dart). + +```dart +import 'package:google_sign_in/google_sign_in.dart'; +import 'dart:io'; + +Future loginWithGoogle() async { + GoogleSignIn googleSignIn; + + if (Platform.isAndroid) { + googleSignIn = GoogleSignIn( + serverClientId: "GOOGLE_WEB_CLIENT_ID", + scopes: [ + 'email', + ], + ); + } else { + googleSignIn = GoogleSignIn( + clientId: "GOOGLE_IOS_CLIENT_ID", + serverClientId: "GOOGLE_WEB_CLIENT_ID", + scopes: [ + 'email', + ], + ); + } + + GoogleSignInAccount? account = await googleSignIn.signIn(); + + if (account == null) { + print("Google sign in was aborted"); + return; + } + + String? authCode = account.serverAuthCode; + + if (authCode == null) { + print("Google sign in did not return a server auth code"); + return; + } + + // Refer to step 2 + } +``` + + + + + +

Step 2) Calling the signinup API to consume the authorisation token

+ + + +Once you have the authorisation code from the auth provider, you need to call the signinup API exposed by our backend SDK as shown below: + +```bash +curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup' \ +--header 'Content-Type: application/json; charset=utf-8' \ +--data-raw '{ + "thirdPartyId": "google", + "clientType": "...", + "redirectURIInfo": { + "redirectURIOnProviderDashboard": "", + "redirectURIQueryParams": { + "code": "...", + } + } +}' +``` + +:::important +When calling the API exposed by the SuperTokens backend SDK, we pass an empty string for `redirectURIOnProviderDashboard` because we use the native login flow using the authorization code which does not involve any redirection on the frontend. +::: + +The response body from the API call has a `status` property in it: +- `status: "OK"`: User sign in / up was successful. The response also contains more information about the user, for example their user ID, and if it was a new user or existing user. +- `status: "NO_EMAIL_GIVEN_BY_PROVIDER"`: This is returned if the social / SSO provider did not provider an email for the user. In this case, you want to ask the user to pick another method of sign in. Or, you can also override the backend functions to create a fake email for the user for this provider. +- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. +- `status: "SIGN_IN_UP_NOT_ALLOWED"`: This can happen during automatic account linking or during MFA. The `reason` prop that's in the response body contains a support code using which you can see why the sign in / up was not allowed. + +:::note +On success, the backend will send back session tokens as part of the response headers which will be automatically handled by our frontend SDK for you. +::: + + + +### Authorization Code Grant flow with PKCE + +This is similar to the first one, except that you do **not** need to provide a client secret during backend init. +This flow only works for providers which support the [PKCE flow](https://oauth.net/2/pkce/). + + + + + +#### Fetching the authorisation token on the frontend + +You can use the [react native auth library](https://github.com/FormidableLabs/react-native-app-auth) to also return the PKCE code verifier along with the authorization code. This can be done by setting the `usePKCE` boolean to `true` and also by setting the `skipCodeExchange` to `true` when configuring the react native auth library. + + + + + +#### Fetching the authorisation token on the frontend + +You can use the [AppAuth-Android](https://github.com/openid/AppAuth-Android) library to use the PKCE flow by using the `setCodeVerifier` method when creating a `AuthorizationRequest`. + + + + + +#### Fetching the authorisation token on the frontend + +You can use the [AppAuth-iOS](https://github.com/openid/AppAuth-iOS) library to use the PKCE flow. + + + + + +#### Fetching the authorisation token on the frontend + +You can use [flutter_appauth](https://pub.dev/packages/flutter_appauth) to use the PKCE flow by providing a `codeVerifier` when you call the `appAuth.token` function. + + + + + +#### Calling the signinup API to consume the authorisation token + + + +Once you have the authorisation code and PKCE verifier from the auth provider, you need to call the signinup API exposed by our backend SDK as shown below: + +```bash +curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "thirdPartyId": "THIRD_PARTY_ID", + "clientType": "...", + "redirectURIInfo": { + "redirectURIOnProviderDashboard": "REDIRECT_URI", + "redirectURIQueryParams": { + "code": "...", + }, + "pkceCodeVerifier": "..." + } +}' +``` + +:::important +- Replace `THIRD_PARTY_ID` with the provider id. The provider id must match the one you configure in the backend when intialising SuperTokens. +- `REDIRECT_URI` must exactly match the value you configure on the providers dashboard. +::: + +The response body from the API call has a `status` property in it: +- `status: "OK"`: User sign in / up was successful. The response also contains more information about the user, for example their user ID, and if it was a new user or existing user. +- `status: "NO_EMAIL_GIVEN_BY_PROVIDER"`: This is returned if the social / SSO provider did not provider an email for the user. In this case, you want to ask the user to pick another method of sign in. Or, you can also override the backend functions to create a fake email for the user for this provider. +- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. +- `status: "SIGN_IN_UP_NOT_ALLOWED"`: This can happen during automatic account linking or during MFA. The `reason` prop that's in the response body contains a support code using which you can see why the sign in / up was not allowed. + +:::note +On success, the backend will send back session tokens as part of the response headers which will be automatically handled by our frontend SDK for you. +::: + + diff --git a/v2/community/reusableMD/custom-ui/frontend-custom-ui-passwordless-magic-link.mdx b/v2/community/reusableMD/custom-ui/frontend-custom-ui-passwordless-magic-link.mdx new file mode 100644 index 000000000..349f4ae9b --- /dev/null +++ b/v2/community/reusableMD/custom-ui/frontend-custom-ui-passwordless-magic-link.mdx @@ -0,0 +1,506 @@ +import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" +import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" +import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" +import WebJsInjector from "/src/components/webJsInjector" +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import AppInfoForm from "/src/components/appInfoForm" + +The following section will show you what aspects you need to cover in order to implement the UI for a `Magic Link` flow. +The same flow applies during either sign up or sign in. +We will show you how to determine if a new user was created in the next steps. + +### 3.1 Sending the Magic link + +You will have to add a form that asks the user for their email addres or phone number. +When the users submits the form you will have to call the following API to create and send them a **Magic Link**. + +:::info + +You will configure the contact method in the next page, where we talk about adding the `SDK` to your backend app. + +::: + + + + + + + + +```tsx +import { createCode } from "supertokens-web-js/recipe/passwordless"; + +async function sendMagicLink(email: string) { + try { + let response = await createCode({ + email + }); + /** + * For phone number, use this: + + let response = await createCode({ + phoneNumber: "+1234567890" + }); + + */ + + if (response.status === "SIGN_IN_UP_NOT_ALLOWED") { + // the reason string is a user friendly message + // about what went wrong. It can also contain a support code which users + // can tell you so you know why their sign in / up was not allowed. + window.alert(response.reason) + } else { + // Magic link sent successfully. + window.alert("Please check your email for the magic link"); + } + } catch (err: any) { + if (err.isSuperTokensGeneralError === true) { + // this may be a custom error message sent from the API by you, + // or if the input email / phone number is not valid. + window.alert(err.message); + } else { + window.alert("Oops! Something went wrong."); + } + } +} +``` + + + + +```tsx +import supertokensPasswordless from "supertokens-web-js-script/recipe/passwordless"; +async function sendMagicLink(email: string) { + try { + let response = await supertokensPasswordless.createCode({ + email + }); + /** + * For phone number, use this: + + let response = await supertokens^{recipeNameCapitalLetters}.createCode({ + phoneNumber: "+1234567890" + }); + + */ + + if (response.status === "SIGN_IN_UP_NOT_ALLOWED") { + // the reason string is a user friendly message + // about what went wrong. It can also contain a support code which users + // can tell you so you know why their sign in / up was not allowed. + window.alert(response.reason) + } else { + // Magic link sent successfully. + window.alert("Please check your email for the magic link"); + } + } catch (err: any) { + if (err.isSuperTokensGeneralError === true) { + // this may be a custom error message sent from the API by you, + // or if the input email / phone number is not valid. + window.alert(err.message); + } else { + window.alert("Oops! Something went wrong."); + } + } +} +``` + + + + + + + + + + +For email based login + +```bash +curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup/code' \ +--header 'Content-Type: application/json; charset=utf-8' \ +--data-raw '{ + "email": "johndoe@gmail.com" +}' +``` + +For phone number based login +```bash +curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup/code' \ +--header 'Content-Type: application/json; charset=utf-8' \ +--data-raw '{ + "phoneNumber": "+1234567890" +}' +``` + + + +The response body from the API call has a `status` property in it: +- `status: "OK"`: This means that the magic link was successfully sent. +- `status: "GENERAL_ERROR"`: This is possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend, or if the input email or password failed the backend validation logic. +- `status: "SIGN_IN_UP_NOT_ALLOWED"`: This can happen during automatic account linking or during MFA. The `reason` prop that's in the response body contains a support code using which you can see why the sign in / up was not allowed. + +The response from the API call is the following object (in case of `status: "OK"`): +```json +{ + status: "OK"; + deviceId: string; + preAuthSessionId: string; + flowType: "USER_INPUT_CODE" | "MAGIC_LINK" | "USER_INPUT_CODE_AND_MAGIC_LINK"; + fetchResponse: Response; // raw fetch response from the API call +} +``` + +You want to save the `deviceId` and `preAuthSessionId` on the frontend storage. These will be useful to: +- Resend a new magic link. +- Detect if the user has already sent a magic link before or if this is an entirely new login attempt. This distinction can be important if you have different UI for these two states. For example, if this info already exists, you do not want to show the user an input box to enter their email / phone, and instead want to show them the resend link button. + + + + + +### 3.2 Resending a Magic Link + +After the initial magic link was sent to the user, you may want to display a resend button to them. +When the user clicks on this button, you should call the following API + + + + + + + +```tsx +import { resendCode, clearLoginAttemptInfo } from "supertokens-web-js/recipe/passwordless"; + +async function resendMagicLink() { + try { + let response = await resendCode(); + + if (response.status === "RESTART_FLOW_ERROR") { + // this can happen if the user has already successfully logged in into + // another device whilst also trying to login to this one. + + // we clear the login attempt info that was added when the createCode function + // was called - so that if the user does a page reload, they will now see the + // enter email / phone UI again. + await clearLoginAttemptInfo(); + window.alert("Login failed. Please try again"); + window.location.assign("/auth") + } else { + // Magic link resent successfully. + window.alert("Please check your email for the magic link"); + } + } catch (err: any) { + if (err.isSuperTokensGeneralError === true) { + // this may be a custom error message sent from the API by you. + window.alert(err.message); + } else { + window.alert("Oops! Something went wrong."); + } + } +} +``` + + + + +```tsx +import supertokensPasswordless from "supertokens-web-js-script/recipe/passwordless"; +async function resendMagicLink() { + try { + let response = await supertokensPasswordless.resendCode(); + + if (response.status === "RESTART_FLOW_ERROR") { + // this can happen if the user has already successfully logged in into + // another device whilst also trying to login to this one. + + // we clear the login attempt info that was added when the createCode function + // was called - so that if the user does a page reload, they will now see the + // enter email / phone UI again. + await supertokensPasswordless.clearLoginAttemptInfo(); + window.alert("Login failed. Please try again"); + window.location.assign("/auth") + } else { + // Magic link resent successfully. + window.alert("Please check your email for the magic link"); + } + } catch (err: any) { + if (err.isSuperTokensGeneralError === true) { + // this may be a custom error message sent from the API by you. + window.alert(err.message); + } else { + window.alert("Oops! Something went wrong."); + } + } +} +``` + + + + + + + + + + +```bash +curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup/code/resend' \ +--header 'Content-Type: application/json; charset=utf-8' \ +--data-raw '{ + "deviceId": "...", + "preAuthSessionId": "...." +}' +``` + + + +The response body from the API call has a `status` property in it: +- `status: "OK"`: This means that the magic link was successfully sent. +- `status: "RESTART_FLOW_ERROR"`: This can happen if the user has already successfully logged in into another device whilst also trying to login to this one. You want to take the user back to the login screen where they can enter their email / phone number again. Be sure to remove the stored `deviceId` and `preAuthSessionId` from the frontend storage. +- `status: "GENERAL_ERROR"`: This is possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. + + + + + +#### How to detect if the user is on step 3.1 or step 3.2? + +If you are building the UI for both of the previous steps on the same page you might run into an issue when the user refreshes the page. +To prevent this you need a way to know which UI to show. + + + + + + + +```tsx +import { getLoginAttemptInfo } from "supertokens-web-js/recipe/passwordless"; + +async function hasInitialMagicLinkBeenSent() { + return await getLoginAttemptInfo() !== undefined; +} +``` + + + + +```tsx +import supertokensPasswordless from "supertokens-web-js-script/recipe/passwordless"; +async function hasInitialMagicLinkBeenSent() { + return await supertokensPasswordless.getLoginAttemptInfo() !== undefined; +} +``` + + + + +If `hasInitialMagicLinkBeenSent` returns `true`, it means that the user has already sent the initial magic link to themselves, and you can show the resend link UI. Else show a form asking them to enter their email / phone number. + + + + + +Since you save the `preAuthSessionId` and `deviceId` after the initial magic link is sent, you can know if the user is on either **step 3.1** or **step 3.2**, by simply checking if these tokens are stored on the device. + +If they aren't, you should follow **step 3.1**, else follow **step 3.2**. + +:::important +You need to clear these tokens if the user navigates away from the **step 3.2** page, or if you get a `RESTART_FLOW_ERROR` at any point in time from an API call, or if the user has successfully logged in. +::: + + + + + +### 3.3 Consuming the Magic Link + +When a user clicks on a magic link you will first have to know if the action came from the same browser/device as the one that started the flow. +To do this you ca use this code sample. + + + + + + + +```tsx +import { getLoginAttemptInfo } from "supertokens-web-js/recipe/passwordless"; + +async function isThisSameBrowserAndDevice() { + return await getLoginAttemptInfo() !== undefined; +} +``` + + + + +```tsx +import supertokensPasswordless from "supertokens-web-js-script/recipe/passwordless"; +async function isThisSameBrowserAndDevice() { + return await supertokensPasswordless.getLoginAttemptInfo() !== undefined; +} +``` + + + + + + + + +Since you save the `preAuthSessionId` and `deviceId`, you can check if they exist on the app. If they do, then it's the same device that the user has opened the link on, else it's a different device. + + + + + +:::important + +Add a intermediate step if the user came from a different device. + +::: + +If the user clicked on a link from a different device you will have to show some kind of an intermediate UI. +This is to protect against email clients opening the magic link on their servers and consuming the link. + +The page should require additional user interaction before consuming the magic link. +For example, you could show a button with the following text: `Click here to login into this device`. +On click, you can consume the magic link to log the user into that device. + +Now that you know how to avoid any potential errors we can go ahead with the actual instructions on how to authenticate with the magic link. + + + + + + + +```tsx +import { consumeCode, clearLoginAttemptInfo } from "supertokens-web-js/recipe/passwordless"; + +async function handleMagicLinkClicked() { + try { + let response = await consumeCode(); + + if (response.status === "OK") { + // we clear the login attempt info that was added when the createCode function + // was called since the login was successful. + await clearLoginAttemptInfo(); + if (response.createdNewRecipeUser && response.user.loginMethods.length === 1) { + // user sign up success + } else { + // user sign in success + } + window.location.assign("/home") + } else { + // this can happen if the magic link has expired or is invalid + // or if it was denied due to security reasons in case of automatic account linking + + // we clear the login attempt info that was added when the createCode function + // was called - so that if the user does a page reload, they will now see the + // enter email / phone UI again. + await clearLoginAttemptInfo(); + window.alert("Login failed. Please try again"); + window.location.assign("/auth") + } + } catch (err: any) { + if (err.isSuperTokensGeneralError === true) { + // this may be a custom error message sent from the API by you. + window.alert(err.message); + } else { + window.alert("Oops! Something went wrong."); + } + } +} +``` + + + + +```tsx +import supertokensPasswordless from "supertokens-web-js-script/recipe/passwordless"; +async function handleMagicLinkClicked() { + try { + let response = await supertokensPasswordless.consumeCode(); + + if (response.status === "OK") { + // we clear the login attempt info that was added when the createCode function + // was called since the login was successful. + await supertokensPasswordless.clearLoginAttemptInfo(); + if (response.createdNewRecipeUser && response.user.loginMethods.length === 1) { + // user sign up success + } else { + // user sign in success + } + window.location.assign("/home") + } else { + // this can happen if the magic link has expired or is invalid + // or if it was denied due to security reasons in case of automatic account linking + + // we clear the login attempt info that was added when the createCode function + // was called - so that if the user does a page reload, they will now see the + // enter email / phone UI again. + await supertokensPasswordless.clearLoginAttemptInfo(); + window.alert("Login failed. Please try again"); + window.location.assign("/auth") + } + } catch (err: any) { + if (err.isSuperTokensGeneralError === true) { + // this may be a custom error message sent from the API by you. + window.alert(err.message); + } else { + window.alert("Oops! Something went wrong."); + } + } +} +``` + + + + + + + + +You need to extract the `linkCode` and `preAuthSessionId` from the Magic link. For example, if the Magic link is + +```text +https://example.com/auth/verify?preAuthSessionId=PyIwyA6VjdjNF5ggMV960rs3QXupRP2PEg2KcN5oi8s=#s4hxpBPnRC3xwBsCkFU228lh_CWe5HUBMRPowajsrgs= +``` + +Then the `preAuthSessionId` is the value of the query param `preAuthSessionId` (`PyIwyA6VjdjNF5ggMV960rs3QXupRP2PEg2KcN5oi8s=` in the example), and the `linkCode` is the part after the `#` (`s4hxpBPnRC3xwBsCkFU228lh_CWe5HUBMRPowajsrgs=` in our example). + +We can then use these to call the consume API + + + +```bash +curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup/code/consume' \ +--header 'Content-Type: application/json; charset=utf-8' \ +--data-raw '{ + "linkCode": "s4hxpBPnRC3xwBsCkFU228lh_CWe5HUBMRPowajsrgs=", + "preAuthSessionId": "PyIwyA6VjdjNF5ggMV960rs3QXupRP2PEg2KcN5oi8s=" +}' +``` + + + + + +For a multi tenancy setup, the `` value can be fetched from `tenantId` query parameter from the magic link. If it's not there in the link, you can use the value `"public"` (which is the default tenant). + + + +The response body from the API call has a `status` property in it: +- `status: "OK"`: User sign in / up was successful. The response also contains more information about the user, for example their user ID, and if it was a new user or existing user. +- `status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" | "RESTART_FLOW_ERROR"`: These responses indicate that the Magic link was invalid or expired. +- `status: "GENERAL_ERROR"`: This is possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. +- `status: "SIGN_IN_UP_NOT_ALLOWED"`: This can happen during automatic account linking or during MFA. The `reason` prop that's in the response body contains a support code using which you can see why the sign in / up was not allowed. + + + + diff --git a/v2/community/reusableMD/custom-ui/frontend-custom-ui-passwordless-otp.mdx b/v2/community/reusableMD/custom-ui/frontend-custom-ui-passwordless-otp.mdx new file mode 100644 index 000000000..3159762a4 --- /dev/null +++ b/v2/community/reusableMD/custom-ui/frontend-custom-ui-passwordless-otp.mdx @@ -0,0 +1,459 @@ + +import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" +import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" +import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" +import WebJsInjector from "/src/components/webJsInjector" +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import AppInfoForm from "/src/components/appInfoForm" + +The following section will show you what aspects you need to cover in order to implement the UI for a `OTP`, One-Time Password, flow. +The same flow applies during either sign up or sign in. +We will show you how to determine if a new user was created in the next steps. + +### 3.1 Creating and Sending the OTP + +You will have to add a form that asks the user for their email addres or phone number. +When the users submits the form you will have to call the following API to create and send them a OTP. + +:::info + +You will configure the contact method in the next page, where we talk about adding the `SDK` to your backend app. + +::: + + + + + + + +```tsx +import { createCode } from "supertokens-web-js/recipe/passwordless"; + +async function sendOTP(email: string) { + try { + let response = await createCode({ + email + }); + /** + * For phone number, use this: + + let response = await createPasswordlessCode({ + phoneNumber: "+1234567890" + }); + + */ + + if (response.status === "SIGN_IN_UP_NOT_ALLOWED") { + // the reason string is a user friendly message + // about what went wrong. It can also contain a support code which users + // can tell you so you know why their sign in / up was not allowed. + window.alert(response.reason) + } else { + // OTP sent successfully. + window.alert("Please check your email for an OTP"); + } + } catch (err: any) { + if (err.isSuperTokensGeneralError === true) { + // this may be a custom error message sent from the API by you, + // or if the input email / phone number is not valid. + window.alert(err.message); + } else { + window.alert("Oops! Something went wrong."); + } + } +} +``` + + + + +```tsx +import supertokensPasswordless from "supertokens-web-js-script/recipe/passwordless"; +async function sendOTP(email: string) { + try { + let response = await supertokensPasswordless.createCode({ + email + }); + /** + * For phone number, use this: + + let response = await supertokens^{recipeNameCapitalLetters}.createCode({ + phoneNumber: "+1234567890" + }); + + */ + + if (response.status === "SIGN_IN_UP_NOT_ALLOWED") { + // the reason string is a user friendly message + // about what went wrong. It can also contain a support code which users + // can tell you so you know why their sign in / up was not allowed. + window.alert(response.reason) + } else { + // OTP sent successfully. + window.alert("Please check your email for an OTP"); + } + } catch (err: any) { + if (err.isSuperTokensGeneralError === true) { + // this may be a custom error message sent from the API by you, + // or if the input email / phone number is not valid. + window.alert(err.message); + } else { + window.alert("Oops! Something went wrong."); + } + } +} +``` + + + + + + + + + + +For email based login + +```bash +curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup/code' \ +--header 'Content-Type: application/json; charset=utf-8' \ +--data-raw '{ + "email": "johndoe@gmail.com" +}' +``` + +For phone number based login +```bash +curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup/code' \ +--header 'Content-Type: application/json; charset=utf-8' \ +--data-raw '{ + "phoneNumber": "+1234567890" +}' +``` + + + +The response body from the API call has a `status` property in it: +- `status: "OK"`: This means that the OTP was successfully sent. +- `status: "GENERAL_ERROR"`: This is possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend, or if the input email or password failed the backend validation logic. +- `status: "SIGN_IN_UP_NOT_ALLOWED"`: This can happen during automatic account linking or during MFA. The `reason` prop that's in the response body contains a support code using which you can see why the sign in / up was not allowed. + +The response from the API call is the following object (in case of `status: "OK"`): +```json +{ + status: "OK"; + deviceId: string; + preAuthSessionId: string; + flowType: "USER_INPUT_CODE" | "MAGIC_LINK" | "USER_INPUT_CODE_AND_MAGIC_LINK"; + fetchResponse: Response; // raw fetch response from the API call +} +``` + +You want to save the `deviceId` and `preAuthSessionId` on the frontend storage. These will be useful to: +- Resend a new OTP. +- Detect if the user has already sent an OTP before or if this is an entirely new login attempt. This distinction can be important if you have different UI for these two states. For example, if this info already exists, you do not want to show the user an input box to enter their email / phone, and instead want to show them the enter OTP form with a resend button. +- Verify the user's input OTP. + + + + + +### 3.2 Resending a OTP + +After the OTP was sent to the user, you may want to display a resend button to them. +When the user clicks on this button, you should call the following API + + + + + + + +```tsx +import { resendCode, clearLoginAttemptInfo } from "supertokens-web-js/recipe/passwordless"; + +async function resendOTP() { + try { + let response = await resendCode(); + + if (response.status === "RESTART_FLOW_ERROR") { + // this can happen if the user has already successfully logged in into + // another device whilst also trying to login to this one. + + // we clear the login attempt info that was added when the createCode function + // was called - so that if the user does a page reload, they will now see the + // enter email / phone UI again. + await clearLoginAttemptInfo(); + window.alert("Login failed. Please try again"); + window.location.assign("/auth") + } else { + // OTP resent successfully. + window.alert("Please check your email for the OTP"); + } + } catch (err: any) { + if (err.isSuperTokensGeneralError === true) { + // this may be a custom error message sent from the API by you. + window.alert(err.message); + } else { + window.alert("Oops! Something went wrong."); + } + } +} +``` + + + + +```tsx +import supertokensPasswordless from "supertokens-web-js-script/recipe/passwordless"; +async function resendOTP() { + try { + let response = await supertokensPasswordless.resendCode(); + + if (response.status === "RESTART_FLOW_ERROR") { + // this can happen if the user has already successfully logged in into + // another device whilst also trying to login to this one. + + // we clear the login attempt info that was added when the createCode function + // was called - so that if the user does a page reload, they will now see the + // enter email / phone UI again. + await supertokensPasswordless.clearLoginAttemptInfo(); + window.alert("Login failed. Please try again"); + window.location.assign("/auth") + } else { + // OTP resent successfully. + window.alert("Please check your email for the OTP"); + } + } catch (err: any) { + if (err.isSuperTokensGeneralError === true) { + // this may be a custom error message sent from the API by you. + window.alert(err.message); + } else { + window.alert("Oops! Something went wrong."); + } + } +} +``` + + + + + + + + + + +```bash +curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup/code/resend' \ +--header 'Content-Type: application/json; charset=utf-8' \ +--data-raw '{ + "deviceId": "...", + "preAuthSessionId": "...." +}' +``` + + + +The response body from the API call has a `status` property in it: +- `status: "OK"`: This means that the OTP was successfully sent. +- `status: "RESTART_FLOW_ERROR"`: This can happen if the user has already successfully logged in into another device whilst also trying to login to this one. You want to take the user back to the login screen where they can enter their email / phone number again. Be sure to remove the stored `deviceId` and `preAuthSessionId` from the frontend storage. +- `status: "GENERAL_ERROR"`: This is possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. + + + + + +#### How to detect if the user is on step 3.1 or step 3.2? + +If you are building the UI for both of the previous steps on the same page you might run into an issue when the user refreshes the page. +To prevent this you need a way to know which UI to show. + + + + + + + +```tsx +import { getLoginAttemptInfo } from "supertokens-web-js/recipe/passwordless"; + +async function hasInitialOTPBeenSent() { + return await getLoginAttemptInfo() !== undefined; +} +``` + + + + +```tsx +import supertokensPasswordless from "supertokens-web-js-script/recipe/passwordless"; +async function hasInitialOTPBeenSent() { + return await supertokensPasswordless.getLoginAttemptInfo() !== undefined; +} +``` + + + + +If `hasInitialOTPBeenSent` returns `true`, it means that the user has already sent the initial OTP to themselves, and you can show the enter OTP form + resend OTP button 3.2. Else show a form asking them to enter their email / phone number 3.1. + + + + + +Since you save the `preAuthSessionId` and `deviceId` after the initial OTP is sent, you can know if the user is in 3.1 vs 3.2 by simply checking if these tokens are stored on the device. + +If they aren't, you should follow 3.1, else follow 3.2. + +:::important +You need to clear these tokens if the user navigates away from the 3.2 page, or if you get a `RESTART_FLOW_ERROR` at any point in time from an API call, or if the user has successfully logged in. +::: + + + + + +### 3.3 Verifying the OTP + +When the user enters an OTP you will have to call the following API to verify it + + + + + + + +```tsx +import { consumeCode, clearLoginAttemptInfo } from "supertokens-web-js/recipe/passwordless"; + +async function handleOTPInput(otp: string) { + try { + let response = await consumeCode({ + userInputCode: otp + }); + + if (response.status === "OK") { + // we clear the login attempt info that was added when the createCode function + // was called since the login was successful. + await clearLoginAttemptInfo(); + if (response.createdNewRecipeUser && response.user.loginMethods.length === 1) { + // user sign up success + } else { + // user sign in success + } + window.location.assign("/home") + } else if (response.status === "INCORRECT_USER_INPUT_CODE_ERROR") { + // the user entered an invalid OTP + window.alert("Wrong OTP! Please try again. Number of attempts left: " + (response.maximumCodeInputAttempts - response.failedCodeInputAttemptCount)); + } else if (response.status === "EXPIRED_USER_INPUT_CODE_ERROR") { + // it can come here if the entered OTP was correct, but has expired because + // it was generated too long ago. + window.alert("Old OTP entered. Please regenerate a new one and try again"); + } else { + // this can happen if the user tried an incorrect OTP too many times. + // or if it was denied due to security reasons in case of automatic account linking + + // we clear the login attempt info that was added when the createCode function + // was called - so that if the user does a page reload, they will now see the + // enter email / phone UI again. + await clearLoginAttemptInfo(); + window.alert("Login failed. Please try again"); + window.location.assign("/auth") + } + } catch (err: any) { + if (err.isSuperTokensGeneralError === true) { + // this may be a custom error message sent from the API by you. + window.alert(err.message); + } else { + window.alert("Oops! Something went wrong."); + } + } +} +``` + + + + +```tsx +import supertokensPasswordless from "supertokens-web-js-script/recipe/passwordless"; +async function handleOTPInput(otp: string) { + try { + let response = await supertokensPasswordless.consumeCode({ + userInputCode: otp + }); + + if (response.status === "OK") { + // we clear the login attempt info that was added when the createCode function + // was called since the login was successful. + await supertokensPasswordless.clearLoginAttemptInfo(); + if (response.createdNewRecipeUser && response.user.loginMethods.length === 1) { + // user sign up success + } else { + // user sign in success + } + window.location.assign("/home") + } else if (response.status === "INCORRECT_USER_INPUT_CODE_ERROR") { + // the user entered an invalid OTP + window.alert("Wrong OTP! Please try again. Number of attempts left: " + (response.maximumCodeInputAttempts - response.failedCodeInputAttemptCount)); + } else if (response.status === "EXPIRED_USER_INPUT_CODE_ERROR") { + // it can come here if the entered OTP was correct, but has expired because + // it was generated too long ago. + window.alert("Old OTP entered. Please regenerate a new one and try again"); + } else { + // this can happen if the user tried an incorrect OTP too many times. + // or if it was denied due to security reasons in case of automatic account linking + + // we clear the login attempt info that was added when the createCode function + // was called - so that if the user does a page reload, they will now see the + // enter email / phone UI again. + await supertokensPasswordless.clearLoginAttemptInfo(); + window.alert("Login failed. Please try again"); + window.location.assign("/auth") + } + } catch (err: any) { + if (err.isSuperTokensGeneralError === true) { + // this may be a custom error message sent from the API by you. + window.alert(err.message); + } else { + window.alert("Oops! Something went wrong."); + } + } +} +``` + + + + + + + + + + +```bash +curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup/code/consume' \ +--header 'Content-Type: application/json; charset=utf-8' \ +--data-raw '{ + "deviceId": "...", + "preAuthSessionId": "...", + "userInputCode": "" +}' +``` + + + +The response body from the API call has a `status` property in it: +- `status: "OK"`: User sign in / up was successful. The response also contains more information about the user, for example their user ID, and if it was a new user or existing user. +- `status: "INCORRECT_USER_INPUT_CODE_ERROR"`: The entered OTP is invalid. The response also contains information about the maximum number of retries and the number of failed attempts so far. +- `status: "EXPIRED_USER_INPUT_CODE_ERROR"`: The entered OTP is too old. You should ask the user to resend a new OTP and try again. +- `status: "RESTART_FLOW_ERROR"`: These responses that the user tried invalid OTPs too many times. +- `status: "GENERAL_ERROR"`: This is possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. +- `status: "SIGN_IN_UP_NOT_ALLOWED"`: This can happen during automatic account linking or during MFA. The `reason` prop that's in the response body contains a support code using which you can see why the sign in / up was not allowed. + + + + diff --git a/v2/community/reusableMD/custom-ui/frontend-custom-ui-passwordless.mdx b/v2/community/reusableMD/custom-ui/frontend-custom-ui-passwordless.mdx new file mode 100644 index 000000000..3a23dde3f --- /dev/null +++ b/v2/community/reusableMD/custom-ui/frontend-custom-ui-passwordless.mdx @@ -0,0 +1,32 @@ + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +import FrontendCustomUIPasswordlessMagicLink from './frontend-custom-ui-passwordless-magic-link.mdx' +import FrontendCustomUIPasswordlessOtp from './frontend-custom-ui-passwordless-otp.mdx'; + + + + + + + + + + + + + + + + + + +On success, the backend will send back session tokens as part of the response headers which will be automatically handled by our frontend SDK for you. diff --git a/v2/community/reusableMD/custom-ui/frontend-custom-ui-session-management.mdx b/v2/community/reusableMD/custom-ui/frontend-custom-ui-session-management.mdx new file mode 100644 index 000000000..242950c87 --- /dev/null +++ b/v2/community/reusableMD/custom-ui/frontend-custom-ui-session-management.mdx @@ -0,0 +1,132 @@ + +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" +import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" +import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" +import TabItem from '@theme/TabItem'; +import {Question, Answer}from "/src/components/question" +import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" + + + + + + + +You can use the `doesSessionExist` function to check if a session exists. + +```tsx +import Session from 'supertokens-web-js/recipe/session'; + +async function doesSessionExist() { + if (await Session.doesSessionExist()) { + // user is logged in + } else { + // user has not logged in yet + } +} +``` + + + + +You can use the `doesSessionExist` function to check if a session exists. + +```tsx +import Session from 'supertokens-web-js-script/recipe/session'; +async function doesSessionExist() { + if (await Session.doesSessionExist()) { + // user is logged in + } else { + // user has not logged in yet + } +} +``` + + + + + + + + + + + + +You can use the `doesSessionExist` function to check if a session exists. + +```tsx +import SuperTokens from 'supertokens-react-native'; + +async function doesSessionExist() { + if (await SuperTokens.doesSessionExist()) { + // user is logged in + } else { + // user has not logged in yet + } +} +``` + + + + + +You can use the `doesSessionExist` function to check if a session exists. + +```kotlin +import android.app.Application +import com.supertokens.session.SuperTokens + +class MainApplication: Application() { + fun doesSessionExist() { + if (SuperTokens.doesSessionExist(this.applicationContext)) { + // user is logged in + } else { + // user has not logged in yet + } + } +} +``` + + + + + +You can use the `doesSessionExist` function to check if a session exists. + +```swift +import UIKit +import SuperTokensIOS + +fileprivate class ViewController: UIViewController { + func doesSessionExist() { + if SuperTokens.doesSessionExist() { + // User is logged in + } else { + // User is not logged in + } + } +} +``` + + + + + +You can use the `doesSessionExist` function to check if a session exists. + +```dart +import 'package:supertokens_flutter/supertokens.dart'; + +Future doesSessionExist() async { + return await SuperTokens.doesSessionExist(); +} +``` + + + + + + + + diff --git a/v2/community/reusableMD/custom-ui/frontend-custom-ui-session-tokens.mdx b/v2/community/reusableMD/custom-ui/frontend-custom-ui-session-tokens.mdx new file mode 100644 index 000000000..0402a5e04 --- /dev/null +++ b/v2/community/reusableMD/custom-ui/frontend-custom-ui-session-tokens.mdx @@ -0,0 +1,419 @@ + +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" +import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" +import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" +import TabItem from '@theme/TabItem'; +import {Question, Answer}from "/src/components/question" + +There are two modes ways in which you can use sessions with SuperTokens: +- Using `httpOnly` cookies +- Authorization bearer token. + +Our frontend SDK uses `httpOnly` cookie based session for websites by default as it secures against tokens theft via XSS attacks. +For other platforms, like mobile apps, we use a bearer token in the `Authorization` header by default. + + + +import NetworkInterceptors from "/session/reusableMD/networkInterceptors.mdx" + +### With the Frontend SDK + + + + +:::success +No action required. +::: + +Our frontend SDK handles everything for you. You only need to make sure that you have called `supertokens.init` before making any network requests. + +Our SDK adds interceptors to `fetch` and `XHR` (used by `axios`) to save and add session tokens from and to the request. + +By default, our web SDKs use cookies to provide credentials. + + + + + + + + + + +Our frontend SDK handles everything for you. You only need to make sure that you have added our network interceptors as shown below + + + +:::note +By default our mobile SDKs use a bearer token in the Authorization header to provide credentials. +::: + + + + + + + + + +```kotlin +import android.app.Application +import com.supertokens.session.SuperTokens +import com.supertokens.session.SuperTokensHttpURLConnection +import com.supertokens.session.SuperTokensPersistentCookieStore +import java.net.URL +import java.net.HttpURLConnection + +class MainApplication: Application() { + override fun onCreate() { + super.onCreate() + // TODO: Make sure to call SuperTokens.init + } + + fun makeRequest() { + val url = URL("") + val connection = SuperTokensHttpURLConnection.newRequest(url, object: SuperTokensHttpURLConnection.PreConnectCallback { + override fun doAction(con: HttpURLConnection?) { + // TODO: Use `con` to set request method, headers etc + } + }) + + // Handle response using connection object, for example: + if (connection.responseCode == 200) { + // TODO: implement + } + } +} +``` + +:::note +When making network requests you do not need to call `HttpURLConnection.connect` because SuperTokens does this for you. +::: + + + + +```kotlin +import android.content.Context +import com.supertokens.session.SuperTokens +import com.supertokens.session.SuperTokensInterceptor +import okhttp3.OkHttpClient +import retrofit2.Retrofit + +class NetworkManager { + fun getClient(context: Context): OkHttpClient { + val clientBuilder = OkHttpClient.Builder() + clientBuilder.addInterceptor(SuperTokensInterceptor()) + // TODO: Make sure to call SuperTokens.init + + val client = clientBuilder.build() + + // REQUIRED FOR RETROFIT ONLY + val instance = Retrofit.Builder() + .baseUrl("") + .client(client) + .build() + + return client + } + + fun makeRequest(context: Context) { + val client = getClient(context) + // Use client to make requests normally + } +} +``` + + + + +:::note +By default our mobile SDKs use a bearer token in the Authorization header to provide credentials. +::: + + + + + + + + + +

Using URLSession.shared

+ +```swift +import Foundation +import SuperTokensIOS + +fileprivate class NetworkManager { + func setupSuperTokensInterceptor() { + URLProtocol.registerClass(SuperTokensURLProtocol.self) + } +} +``` + +

Using a custom URLSession instance

+ +```swift +import Foundation +import SuperTokensIOS + +fileprivate class NetworkManager { + func setupSuperTokensInterceptor() { + let configuration = URLSessionConfiguration.default + configuration.protocolClasses = [SuperTokensURLProtocol.self] + let session = URLSession(configuration: configuration) + + // Use session when making network requests + } +} +``` + +
+ + + +```swift +import Foundation +import SuperTokensIOS +import Alamofire + +fileprivate class NetworkManager { + func setupSuperTokensInterceptor() { + let configuration = URLSessionConfiguration.af.default + configuration.protocolClasses = [SuperTokensURLProtocol.self] + (configuration.protocolClasses ?? []) + let session = Session(configuration: configuration) + + // Use session when making network requests + } +} +``` + + + +
+ +:::note +By default our mobile SDKs use a bearer token in the Authorization header to provide credentials. +::: + +
+ + + + + + +You can make requests as you normally would with `http`, the only difference is that you import the client from the supertokens package instead. + +```dart +// Import http from the SuperTokens package +import 'package:supertokens_flutter/http.dart' as http; + +Future makeRequest() async { + Uri uri = Uri.parse("http://localhost:3001/api"); + var response = await http.get(uri); + // handle response +} +``` + +

Using a custom http client

+ +If you use a custom http client and want to use SuperTokens, you can simply provide the SDK with your client. All requests will continue to use your client along with the session logic that SuperTokens provides. + +```dart +// Import http from the SuperTokens package +import 'package:supertokens_flutter/http.dart' as http; + +Future makeRequest() async { + Uri uri = Uri.parse("http://localhost:3001/api"); + + // Initialise your custom client + var customClient = http.Client(); + // provide your custom client to SuperTokens + var httpClient = http.Client(client: customClient); + + var response = await httpClient.get(uri); + // handle response +} +``` + +
+ + +

Add the SuperTokens interceptor

+ +Use the extension method provided by the SuperTokens SDK to enable interception on your Dio client. This allows the SuperTokens SDK to handle session tokens for you. + +```dart +import 'package:supertokens_flutter/dio.dart'; +import 'package:dio/dio.dart'; + +void setup() { + Dio dio = Dio(); // Create a Dio instance. + dio.addSupertokensInterceptor(); +} +``` + +

Making network requests

+ +You can make requests as you normally would with `dio`. + +```dart +import 'package:supertokens_flutter/dio.dart'; +import 'package:dio/dio.dart'; + +void setup() { + Dio dio = Dio( + // Provide your config here + ); + dio.addSupertokensInterceptor(); + + var response = dio.get("http://localhost:3001/api"); + // handle response +} +``` + +
+
+ +:::note +By default our mobile SDKs use a bearer token in the Authorization header to provide credentials. +::: + +
+ +
+ +
+
+ +### Without the Frontend SDK + +:::caution +We highly recommend using our frontend SDK to handle session token management. It will save you a lot of time. +::: + +In this case, you will need to manually handle the tokens and session refreshing, and decide if you are going to use header or cookie-based sessions. + +For browsers, we recommend cookies, while for mobile apps (or if you don't want to use the built-in cookie manager) you should use header-based sessions. + + + + + +#### During the Login Action + +You should attach the `st-auth-mode` header to calls to the login API, but this header is safe to attach to all requests. In this case it should be set to "cookie". + +The login API will return the following headers: +- `Set-Cookie`: This will contain the `sAccessToken`, `sRefreshToken` cookies which will be `httpOnly` and will be automatically mananaged by the browser. For mobile apps, you will need to setup cookie handling yourself, use our SDK or use a header based authentication mode. +- `front-token` header: This contains information about the access token: + - The userID + - The expiry time of the access token + - The payload added by you in the access token. + + Here is the structure of the token: + ```tsx + let frontTokenFromRequestHeader = "..."; + let frontTokenDecoded = JSON.parse(decodeURIComponent(escape(atob(frontTokenFromRequestHeader)))); + console.log(frontTokenDecoded); + /* + { + ate: 1665226412455, // time in milliseconds for when the access token will expire, and then a refresh is required + uid: "....", // user ID + up: { + sub: "..", + iat: .., + ... // other access token payload + } + } + + */ + ``` + + This token is mainly used for cookie based auth because you don't have access to the actual access token on the frontend (for security reasons), but may want to read its payload (for example to know the user's role). This token itself is not signed and hence can't be used in place of the access token itself. You may want to save this token in localstorage or in frontend cookies (using `document.cookies`). + +- `anti-csrf` header (optional): By default it's not required, so it's not sent. But if this is sent, you should save this token as well for use when making requests. + +#### When You Make Network Requests to Protected APIs + +The `sAccessToken` will get attached to the request automatically by the browser. Other than that, you need to add the following headers to the request: +- `rid: "anti-csrf"` - this prevents against anti-CSRF requests. If your `apiDomain` and `websiteDomain` values are exactly the same, then this is not necessary. +- `anti-csrf` header (optional): If this was provided to you during login, then you need to add that token as the value of this header. +- You need to set the `credentials` header to `true` or `include`. This is achieved different based on the library you are using to make requests. + +An API call can potentially update the `sAccessToken` and `front-token` tokens, for example if you call the `mergeIntoAccessTokenPayload` function on the `session` object on the backend. This kind of update is reflected in the response headers for your API calls. The headers will contain new values for: +- `sAccessToken`: This will be as a new `Set-Cookie` header and will be managed by the browser automatically. +- `front-token`: This should be read and saved by you in the same way as it's being done during login. + +#### Handling session refreshing + +If any of your API calls return with a status code of `401`, it means that the access token has expired. This will require you to refresh the session before retrying the same API call. + +You can call the refresh API as follows: + + + +```bash +curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/session/refresh' \ +--header 'Cookie: sRefreshToken=...' +``` + +:::note +- You may also need to add the `anti-csrf` header to the request if that was provided to you during sign in. +- The cURL command above shows the `sRefreshToken` cookie as well, but this is added by the web browser automatically, so you don't need to add it explicitly. +::: + + + +The result of a session refresh will be either: +- Status code `200`: This implies a succesful refresh. The set of tokens returned here will be the same as when the user logs in, so you can handle them in the same way. +- Status code `401`: This means that the refresh token is invalid, or has been revoked. You must ask the user to relogin. Remember to clear the `front-token` that you saved on the frontend earlier. + + + + + +##### During the Login Action + +You should attach the `st-auth-mode` header to calls to the login API, but this header is safe to attach to all requests. In this case it should be set to "header". + +The login API will return the following headers: +- `st-access-token`: This contains the current access token associated with the session. You should save this in your application (e.g., in frontend localstorage). +- `st-refresh-token`: This contains the current refresh token associated with the session. You should save this in your application (e.g., in frontend localstorage). + +#### When You Make Network Requests to Protected APIs + +You need to add the following headers to request: +- `authorization: Bearer {access-token}` +- You need to set the `credentials` to `true` or `include`. This is achieved different based on the library you are using to make requests. + +An API call can potentially update the `access-token`, for example if you call the `mergeIntoAccessTokenPayload` function on the `session` object on the backend. This kind of update is reflected in the response headers for your API calls. The headers will contain new values for `st-access-token` + +These should be read and saved by you in the same way as it's being done during login. + +#### Handling session refreshing + +If any of your API calls return with a status code of `401`, it means that the access token has expired. This will require you to refresh the session before retrying the same API call. + +You can call the refresh API as follows: + + + +```bash +curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/session/refresh' \ +--header 'authorization: Bearer {refresh-token}' +``` + + + +The result of a session refresh will be either: +- Status code `200`: This implies a succesful refresh. The set of tokens returned here will be the same as when the user logs in, so you can handle them in the same way. +- Status code `401`: This means that the refresh token is invalid, or has been revoked. You must ask the user to relogin. Remember to clear the `st-refresh-token` and `st-access-token` that you saved on the frontend earlier. + + + + diff --git a/v2/community/reusableMD/custom-ui/frontend-custom-ui-signout.mdx b/v2/community/reusableMD/custom-ui/frontend-custom-ui-signout.mdx new file mode 100644 index 000000000..afa901d88 --- /dev/null +++ b/v2/community/reusableMD/custom-ui/frontend-custom-ui-signout.mdx @@ -0,0 +1,126 @@ + +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" +import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" +import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" +import TabItem from '@theme/TabItem'; +import {Question, Answer}from "/src/components/question" +import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" + +The `signOut` method revokes the session on the frontend and on the backend. Calling this function without a valid session also yields a successful response. + + + + + + + +```tsx +import Session from "supertokens-web-js/recipe/session"; + +async function logout () { + // highlight-next-line + await Session.signOut(); + window.location.href = "/auth"; // or to wherever your logic page is +} +``` + + + + +```tsx +import supertokensSession from "supertokens-web-js-script/recipe/session"; +async function logout () { + // highlight-next-line + await supertokensSession.signOut(); + window.location.href = "/auth"; // or to wherever your logic page is +} +``` + + + + + + + + + + + + +```tsx +import SuperTokens from "supertokens-react-native"; + +async function logout () { + // highlight-next-line + await SuperTokens.signOut(); + // navigate to the login screen.. +} +``` + + + + + +```kotlin +import android.app.Application +import com.supertokens.session.SuperTokens + +class MainApplication: Application() { + fun logout() { + // highlight-next-line + SuperTokens.signOut(this); + // navigate to the login screen.. + } +} +``` + + + + + +```swift +import UIKit +import SuperTokensIOS + +fileprivate class ViewController: UIViewController { + func signOut() { + SuperTokens.signOut(completionHandler: { + error in + + if error != nil { + // handle error + } else { + // Signed out successfully + } + }) + } +} +``` + + + + + +```dart +import 'package:supertokens_flutter/supertokens.dart'; + +Future signOut() async { + await SuperTokens.signOut( + completionHandler: (error) { + // handle error if any + } + ); +} +``` + + + + + + + + + +- On success, the `signOut` function does not redirect the user to another page, so you must redirect the user yourself. +- The `signOut` function calls the signout API exposed by the session recipe on the backend. +- If you call the `signOut` function whilst the access token has expired, but the refresh token still exists, our SDKs will do an automatic session refresh before revoking the session. diff --git a/v2/community/reusableMD/custom-ui/frontend-custom-ui-thirdparty.mdx b/v2/community/reusableMD/custom-ui/frontend-custom-ui-thirdparty.mdx new file mode 100644 index 000000000..e0fa21a81 --- /dev/null +++ b/v2/community/reusableMD/custom-ui/frontend-custom-ui-thirdparty.mdx @@ -0,0 +1,85 @@ +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" +import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" + +import FrontendCustomUIWebAuthorizationCodeFlow from './frontend-custom-ui-web-authorization-code-flow.mdx' +import FrontendCustomUIMobileAuthorizationCodeFlow from './frontend-custom-ui-mobile-authorization-code-flow.mdx' +import FrontendCustomUIMobileAccessTokenFlow from './frontend-custom-ui-mobile-access-token-flow.mdx' + +The **ThirdParty** flow involves creating a UI element that allows the user to initiate the login process. +This is usually done through a separate button for each provider that you have configured. You can have a look at our [UI implementation](https://master--6571be2867f75556541fde98.chromatic.com/?path=/story/thirdparty-auth--sign-in-up) to get a better idea. + + +After the user clicks one of those buttons the actions that you need to take will differ based on which type of authentication scenario you are using: + +- **Authorization Code** + +This option can either involve a **Client Secret** that was configured on the backend or rely on **PCKE** exchange. +The difference between the two is that the first option you will be using a private secret, on the backend, to get the access token. +Whereas the second one makes use of the **PCKE** flow to perform the token exchange. +Regardless of which authenticatio type you are using, in the end, the access token will be used to fetch the user info and log them in. + +- **OAuth/Access Tokens** + +This option only applies to mobile/desktop apps. +The Access Token is obtained on the frontend and then sent to the backend. +SuperTokens then fetches user info using the access token and logs them in. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +:::info + +This flow is not applicable for web apps. + +::: + + + + + + + + + + + + + + diff --git a/v2/community/reusableMD/custom-ui/frontend-custom-ui-web-authorization-code-flow.mdx b/v2/community/reusableMD/custom-ui/frontend-custom-ui-web-authorization-code-flow.mdx new file mode 100644 index 000000000..081f422fb --- /dev/null +++ b/v2/community/reusableMD/custom-ui/frontend-custom-ui-web-authorization-code-flow.mdx @@ -0,0 +1,269 @@ + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import AppInfoForm from "/src/components/appInfoForm" +import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" + +### Redirecting to a Social/SSO Provider + +The first step is to fetch the URL on which the user will be authenticated. This can be done by querying the backend API exposed by SuperTokens (as shown below). The backend SDK automatically appends the right query params to the URL (like scope, client ID etc). + +After we get the URL, we simply redirect the user there. In the code below, we will take an example of login with Google: + + + + + +```tsx +import { getAuthorisationURLWithQueryParamsAndSetState } from "supertokens-web-js/recipe/thirdparty"; + +async function googleSignInClicked() { + try { + const authUrl = await getAuthorisationURLWithQueryParamsAndSetState({ + thirdPartyId: "google", + + // This is where Google should redirect the user back after login or error. + // This URL goes on the Google's dashboard as well. + frontendRedirectURI: "http:///auth/callback/google", + }); + + /* + Example value of authUrl: https://accounts.google.com/o/oauth2/v2/auth/oauthchooseaccount?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email&access_type=offline&include_granted_scopes=true&response_type=code&client_id=1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com&state=5a489996a28cafc83ddff&redirect_uri=https%3A%2F%2Fsupertokens.io%2Fdev%2Foauth%2Fredirect-to-app&flowName=GeneralOAuthFlow + */ + + // we redirect the user to google for auth. + window.location.assign(authUrl); + } catch (err: any) { + if (err.isSuperTokensGeneralError === true) { + // this may be a custom error message sent from the API by you. + window.alert(err.message); + } else { + window.alert("Oops! Something went wrong."); + } + } +} +``` + + + + +```tsx +import supertokensThirdParty from "supertokens-web-js-script/recipe/thirdparty"; +async function googleSignInClicked() { + try { + const authUrl = await supertokensThirdParty.getAuthorisationURLWithQueryParamsAndSetState({ + thirdPartyId: "google", + + // This is where Google should redirect the user back after login or error. + // This URL goes on the Google's dashboard as well. + frontendRedirectURI: "http:///auth/callback/google", + }); + + /* + Example value of authUrl: https://accounts.google.com/o/oauth2/v2/auth/oauthchooseaccount?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email&access_type=offline&include_granted_scopes=true&response_type=code&client_id=1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com&state=5a489996a28cafc83ddff&redirect_uri=https%3A%2F%2Fsupertokens.io%2Fdev%2Foauth%2Fredirect-to-app&flowName=GeneralOAuthFlow + */ + + // we redirect the user to google for auth. + window.location.assign(authUrl); + } catch (err: any) { + if (err.isSuperTokensGeneralError === true) { + // this may be a custom error message sent from the API by you. + window.alert(err.message); + } else { + window.alert("Oops! Something went wrong."); + } + } +} +``` + + + + +### Handling the Auth Callback on Your Frontend + +Once the third party provider redirects your user back to your app, you need to consume the information to sign in the user. This requires you to: +- Setup a route in your app that will handle this callback. We recommend something like `http:///auth/callback/google` (for Google). Regardless of what you make this path, remember to use that same path when calling the `getAuthorisationURLWithQueryParamsAndSetState` function in the first step. + +- On that route, call the following function on page load + + + + +```tsx +import { signInAndUp } from "supertokens-web-js/recipe/thirdparty"; + +async function handleGoogleCallback() { + try { + const response = await signInAndUp(); + + if (response.status === "OK") { + console.log(response.user) + if (response.createdNewRecipeUser && response.user.loginMethods.length === 1) { + // sign up successful + } else { + // sign in successful + } + window.location.assign("/home"); + } else if (response.status === "SIGN_IN_UP_NOT_ALLOWED") { + // the reason string is a user friendly message + // about what went wrong. It can also contain a support code which users + // can tell you so you know why their sign in / up was not allowed. + window.alert(response.reason) + } else { + // SuperTokens requires that the third party provider + // gives an email for the user. If that's not the case, sign up / in + // will fail. + + // As a hack to solve this, you can override the backend functions to create a fake email for the user. + + window.alert("No email provided by social login. Please use another form of login"); + window.location.assign("/auth"); // redirect back to login page + } + } catch (err: any) { + if (err.isSuperTokensGeneralError === true) { + // this may be a custom error message sent from the API by you. + window.alert(err.message); + } else { + window.alert("Oops! Something went wrong."); + } + } +} +``` + + + + +```tsx +import supertokensThirdParty from "supertokens-web-js-script/recipe/thirdparty"; +async function handleGoogleCallback() { + try { + const response = await supertokensThirdParty.signInAndUp(); + + if (response.status === "OK") { + console.log(response.user) + if (response.createdNewRecipeUser && response.user.loginMethods.length === 1) { + // sign up successful + } else { + // sign in successful + } + window.location.assign("/home"); + } else if (response.status === "SIGN_IN_UP_NOT_ALLOWED") { + // the reason string is a user friendly message + // about what went wrong. It can also contain a support code which users + // can tell you so you know why their sign in / up was not allowed. + window.alert(response.reason) + } else { + // SuperTokens requires that the third party provider + // gives an email for the user. If that's not the case, sign up / in + // will fail. + + // As a hack to solve this, you can override the backend functions to create a fake email for the user. + + window.alert("No email provided by social login. Please use another form of login"); + window.location.assign("/auth"); // redirect back to login page + } + } catch (err: any) { + if (err.isSuperTokensGeneralError === true) { + // this may be a custom error message sent from the API by you. + window.alert(err.message); + } else { + window.alert("Oops! Something went wrong."); + } + } +} +``` + + + + +:::note +On success, the backend will send back session tokens as part of the response headers which will be automatically handled by our frontend SDK for you. +::: + +#### Special case for login with Apple + + + +Unlike other providers, Apple will not redirect your user back to your frontend app. Instead, it will redirect the user to your backend with a `FORM POST` request. This means that the URL that you configure on the Apple's dashboard should point to your backend API layer in which **our middleware** will handle the request and redirect the user to your frontend app. Your frontend app should then call the `signInAndUp` API on that page as shown previously. + +In order to tell SuperTokens which frontend route to redirect the user back to, you need to set the `frontendRedirectURI` to the frontend route (just like for other providers), and also need to set the `redirectURIOnProviderDashboard` to point to your backend API route (to which Apple will send a POST request). + + + + +```tsx +import { getAuthorisationURLWithQueryParamsAndSetState } from "supertokens-web-js/recipe/thirdparty"; + +async function appleSignInClicked() { + try { + const authUrl = await getAuthorisationURLWithQueryParamsAndSetState({ + thirdPartyId: "apple", + + frontendRedirectURI: "http://localhost:3000/auth/callback/apple", // This is an example callback URL on your frontend. You can use another path as well. + // highlight-start + redirectURIOnProviderDashboard: "^{form_apiDomain}^{form_apiBasePath}/callback/apple", // This URL goes on the Apple's dashboard + // highlight-end + }); + + // we redirect the user to apple for auth. + window.location.assign(authUrl); + } catch (err: any) { + if (err.isSuperTokensGeneralError === true) { + // this may be a custom error message sent from the API by you. + window.alert(err.message); + } else { + window.alert("Oops! Something went wrong."); + } + } +} +``` + + + + + +```tsx +import supertokensThirdParty from "supertokens-web-js-script/recipe/thirdparty"; +async function appleSignInClicked() { + try { + const authUrl = await supertokensThirdParty.getAuthorisationURLWithQueryParamsAndSetState({ + thirdPartyId: "apple", + + frontendRedirectURI: "http://localhost:3000/auth/callback/apple", // This is an example callback URL on your frontend. You can use another path as well. + // highlight-start + redirectURIOnProviderDashboard: "^{form_apiDomain}^{form_apiBasePath}/callback/apple", // This URL goes on the Apple's dashboard + // highlight-end + }); + + /* + Example value of authUrl: https://accounts.google.com/o/oauth2/v2/auth/oauthchooseaccount?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email&access_type=offline&include_granted_scopes=true&response_type=code&client_id=1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com&state=5a489996a28cafc83ddff&redirect_uri=https%3A%2F%2Fsupertokens.io%2Fdev%2Foauth%2Fredirect-to-app&flowName=GeneralOAuthFlow + */ + + // we redirect the user to google for auth. + window.location.assign(authUrl); + } catch (err: any) { + if (err.isSuperTokensGeneralError === true) { + // this may be a custom error message sent from the API by you. + window.alert(err.message); + } else { + window.alert("Oops! Something went wrong."); + } + } +} +``` + + + + + + +:::info + +If you are using the **Authorization Code Grant** flow with **PCKE** you do **not** need to provide a client secret during backend init. +This only works for providers which support the [PKCE flow](https://oauth.net/2/pkce/). + +::: + diff --git a/v2/community/reusableMD/custom-ui/frontent-custom-ui-sdk-install.mdx b/v2/community/reusableMD/custom-ui/frontent-custom-ui-sdk-install.mdx new file mode 100644 index 000000000..91a29ad20 --- /dev/null +++ b/v2/community/reusableMD/custom-ui/frontent-custom-ui-sdk-install.mdx @@ -0,0 +1,120 @@ +import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" +import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" +import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" +import WebJsInjector from "/src/components/webJsInjector" +import TabItem from '@theme/TabItem'; + +Use the following command to install the required package. + + + + + + + +```bash +npm i -s supertokens-web-js +``` + + + + +You need to add all of the following scripts to your app + + + +```bash + + + + +``` + + + + + + + + + + +:::info + +If you want to implement a common authencation experience for both web and mobile, please look at our [**Unified Login guide**](/docs/unified-login/introduction). + +::: + + + + + +```bash +npm i -s supertokens-react-native +# IMPORTANT: If you already have @react-native-async-storage/async-storage as a dependency, make sure the version is 1.12.1 or higher +npm i -s @react-native-async-storage/async-storage +``` + + + + + +Add to your `settings.gradle`: +```bash +dependencyResolutionManagement { + ... + repositories { + ... + maven { url 'https://jitpack.io' } + } +} +``` + +Add the following to you app level's `build.gradle`: +```bash +implementation 'com.github.supertokens:supertokens-android:X.Y.Z' +``` + +You can find the latest version of the SDK [here](https://github.com/supertokens/supertokens-android/releases) (ignore the `v` prefix in the releases). + + + + + +#### Using Cocoapods + +Add the Cocoapod dependency to your Podfile + +```bash +pod 'SuperTokensIOS' +``` + +#### Using Swift Package Manager + +Follow the [official documentation](https://developer.apple.com/documentation/xcode/adding-package-dependencies-to-your-app) to learn how to use Swift Package Manager to add dependencies to your project. + +When adding the dependency use the `master` branch after you enter the supertokens-ios repository URL: + +```bash +https://github.com/supertokens/supertokens-ios +``` + + + + + +Add the dependency to your pubspec.yaml + +```bash +supertokens_flutter: ^X.Y.Z +``` + +You can find the latest version of the SDK [here](https://github.com/supertokens/supertokens-flutter/releases) (ignore the `v` prefix in the releases). + + + + + + + + diff --git a/v2/community/reusableMD/frontend-sdk-install.mdx b/v2/community/reusableMD/frontend-sdk-install.mdx new file mode 100644 index 000000000..74335cc09 --- /dev/null +++ b/v2/community/reusableMD/frontend-sdk-install.mdx @@ -0,0 +1,120 @@ +--- +show_ui_switcher: true +--- + +import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" +import TabItem from '@theme/TabItem'; +import {Question, Answer}from "/src/components/question" +import AppInfoForm from "/src/components/appInfoForm" +import RRDVersionSubTabs from "/src/components/tabs/RRDVersionSubTabs" +import NpmVersionOrYarnSubTabs from "/src/components/tabs/NpmVersionOrYarnSubTabs" +import PreBuiltUIInjector from "/src/components/prebuiltuiInjector" + + + + + + +```bash +npm i -s supertokens-auth-react +``` + + + + + +```bash +npm i -s supertokens-auth-react supertokens-web-js +``` + + + + +```bash +yarn add supertokens-auth-react supertokens-web-js +``` + + + + + + + + + +Start by installing the SuperTokens Web SDK: + +```bash +npm i -s supertokens-web-js +``` + + + + + +Start by installing the SuperTokens Web SDK: + +```bash +npm i -s supertokens-web-js +``` + + + + + +Start by installing the SuperTokens Web SDK: + +```bash +yarn add supertokens-web-js +``` + + + + + + + + + + + +Start by installing the SuperTokens web SDK: + +```bash +npm i -s supertokens-web-js +``` + + + + + +Start by installing the SuperTokens web SDK: + +```bash +npm i -s supertokens-web-js +``` + + + + + +Start by installing the SuperTokens web SDK: + +```bash +yarn add supertokens-web-js +``` + + + + + + + + +:::important +SuperTokens does not support pre-built UI for mobile frameworks. Please toggle the **Custom UI** option from the *Table of Contents* section. +::: + + + + diff --git a/v2/community/reusableMD/passwordless-quickstart-backend-custom-magic-link.mdx b/v2/community/reusableMD/passwordless-quickstart-backend-custom-magic-link.mdx new file mode 100644 index 000000000..8ce6789bd --- /dev/null +++ b/v2/community/reusableMD/passwordless-quickstart-backend-custom-magic-link.mdx @@ -0,0 +1,148 @@ + +import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import AppInfoForm from "/src/components/appInfoForm" +import CustomAdmonition from "/src/components/customAdmonition" + + +### Change the Magic Link URL, or deep link it to your app + +This step is optional. +By default, the magic link will point to the `websiteDomain` that is configured on the backend, on the `/auth/verify` route (where `/auth` is the default value of `websiteBasePath`). +If you want to change this to a different path, a different domain, or deep link it to your mobile/desktop app, then you can use the following snippet. + + + + +```tsx +import SuperTokens from "supertokens-node"; +import Passwordless from "supertokens-node/recipe/passwordless"; +import Session from "supertokens-node/recipe/session"; + +SuperTokens.init({ + appInfo: { + apiDomain: "...", + appName: "...", + websiteDomain: "..." + }, + recipeList: [ + Passwordless.init({ + contactMethod: "EMAIL", // This example will work with any contactMethod + // This example works with the "USER_INPUT_CODE_AND_MAGIC_LINK" and "MAGIC_LINK" flows. + flowType: "USER_INPUT_CODE_AND_MAGIC_LINK", + + // highlight-start + emailDelivery: { + // highlight-start + override: (originalImplementation) => { + return { + ...originalImplementation, + sendEmail: async function (input) { + return originalImplementation.sendEmail({ + ...input, + urlWithLinkCode: input.urlWithLinkCode?.replace( + // This is: `${websiteDomain}${websiteBasePath}/verify` + "http://localhost:3000/auth/verify", + "http://your.domain.com/your/path" + ) + }) + } + } + } + } + // highlight-end + }), + Session.init({ /* ... */ }) + ] +}); +``` + + + +```go +import ( + "strings" + + "github.com/supertokens/supertokens-golang/ingredients/emaildelivery" + "github.com/supertokens/supertokens-golang/recipe/passwordless" + "github.com/supertokens/supertokens-golang/recipe/passwordless/plessmodels" + "github.com/supertokens/supertokens-golang/supertokens" +) + +func main() { + supertokens.Init(supertokens.TypeInput{ + RecipeList: []supertokens.Recipe{ + passwordless.Init(plessmodels.TypeInput{ + EmailDelivery: &emaildelivery.TypeInput{ + // highlight-start + Override: func(originalImplementation emaildelivery.EmailDeliveryInterface) emaildelivery.EmailDeliveryInterface { + ogSendEmail := *originalImplementation.SendEmail + (*originalImplementation.SendEmail) = func(input emaildelivery.EmailType, userContext supertokens.UserContext) error { + // By default: `${websiteDomain}/${websiteBasePath}/verify` + newUrl := strings.Replace( + *input.PasswordlessLogin.UrlWithLinkCode, + "http://localhost:3000/auth/verify", + "http://localhost:3000/custom/path", + 1, + ) + input.PasswordlessLogin.UrlWithLinkCode = &newUrl + return ogSendEmail(input, userContext) + } + return originalImplementation + }, + // highlight-end + }, + }), + }, + }) +} +``` + + + + +```python +from supertokens_python import init, InputAppInfo +from supertokens_python.recipe.passwordless.types import EmailDeliveryOverrideInput, EmailTemplateVars +from supertokens_python.recipe import passwordless +from typing import Dict, Any +from supertokens_python.ingredients.emaildelivery.types import EmailDeliveryConfig + +def custom_email_deliver(original_implementation: EmailDeliveryOverrideInput) -> EmailDeliveryOverrideInput: + original_send_email = original_implementation.send_email + + # highlight-start + async def send_email(template_vars: EmailTemplateVars, user_context: Dict[str, Any]) -> None: + assert template_vars.url_with_link_code is not None + # By default: `${websiteDomain}/${websiteBasePath}/verify` + template_vars.url_with_link_code = template_vars.url_with_link_code.replace( + "http://localhost:3000/auth/verify", "http://localhost:3000/custom/path") + return await original_send_email(template_vars, user_context) + # highlight-end + + original_implementation.send_email = send_email + return original_implementation + +init( + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework='...', # type: ignore + recipe_list=[ + passwordless.init( + contact_config="", # type: ignore # typecheck-only, removed from output + flow_type="USER_INPUT_CODE", # typecheck-only, removed from output + email_delivery=EmailDeliveryConfig(override=custom_email_deliver) + ) + ] +) +``` + + + + + + +For a multi tenant setup, the input of the overriden function will also contain the `tenantId`. +You can use this to determine the correct value to set for the `websiteDomain` in the generated link. + + diff --git a/v2/emailpassword/common-customizations/email-verification/about.mdx b/v2/emailpassword/common-customizations/email-verification/about.mdx index ceb8fd555..6505435f5 100644 --- a/v2/emailpassword/common-customizations/email-verification/about.mdx +++ b/v2/emailpassword/common-customizations/email-verification/about.mdx @@ -5,11 +5,9 @@ hide_title: true show_ui_switcher: true --- - - - import {PreBuiltOrCustomUISwitcher, PreBuiltUIContent, CustomUIContent} from "/src/components/preBuiltOrCustomUISwitcher" import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" +import WebJsInjector from "/src/components/webJsInjector" import {Question, Answer}from "/src/components/question" import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs"; @@ -19,6 +17,11 @@ import AngularUIImplementation from "/src/components/reusableSnippets/angularUII import VueUIImplementation from "/src/components/reusableSnippets/vueUIImplementation" import { OAuthEmailVerificationDisclaimer }from "/src/components/OAuthDisclaimer" +import CustomAdmonition from "/src/components/customAdmonition" +import NodeJSFrameworkSubTabs from "/src/components/tabs/NodeJSFrameworkSubTabs"; +import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" +import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs"; + # Enable email verification @@ -275,7 +278,1196 @@ Additionally, note that SuperTokens does not send verification emails post user -See our guide [in the custom UI section](../../custom-ui/enable-email-verification). + +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 + + + + +```tsx +import SuperTokens from "supertokens-node"; +import EmailVerification from "supertokens-node/recipe/emailverification"; +import Session from "supertokens-node/recipe/session"; + +SuperTokens.init({ + appInfo: { + apiDomain: "...", + appName: "...", + websiteDomain: "...", + }, + recipeList: [ + // highlight-start + EmailVerification.init({ + mode: "REQUIRED", // or "OPTIONAL" + }), + // highlight-end + Session.init(), + ], +}); +``` + + + + +```go +import ( + "github.com/supertokens/supertokens-golang/recipe/emailverification" + "github.com/supertokens/supertokens-golang/recipe/emailverification/evmodels" + "github.com/supertokens/supertokens-golang/recipe/session" + "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" + "github.com/supertokens/supertokens-golang/supertokens" +) + +func main() { + supertokens.Init(supertokens.TypeInput{ + RecipeList: []supertokens.Recipe{ + // highlight-start + emailverification.Init(evmodels.TypeInput{ + Mode: evmodels.ModeRequired, // or evmodels.ModeOptional + }), + // highlight-end + session.Init(&sessmodels.TypeInput{}), + }, + }) +} +``` + + + + +```python +from supertokens_python import init, InputAppInfo +from supertokens_python.recipe import session +from supertokens_python.recipe import emailverification + +init( + app_info=InputAppInfo( + api_domain="...", app_name="...", website_domain="..."), + framework='...', # type: ignore + recipe_list=[ + # highlight-start + emailverification.init(mode='REQUIRED'), # or 'OPTIONAL' + # highlight-end + session.init() + ] +) +``` + + + + + + +## Step 2: Frontend setup + + + + + + + +```tsx +import SuperTokens from "supertokens-web-js"; +import EmailVerification from "supertokens-web-js/recipe/emailverification"; +import Session from "supertokens-web-js/recipe/session"; + +SuperTokens.init({ + appInfo: { + apiDomain: "...", + appName: "...", + }, + recipeList: [ + // highlight-start + EmailVerification.init(), + Session.init(), + ], +}); +``` + + + + +Add the following ` +``` + + + +Then call the `supertokensEmailVerification.init` function as shown below + +```tsx +import supertokens from "supertokens-web-js-script"; +import supertokensEmailVerification from "supertokens-web-js-script/recipe/emailverification"; +import supertokensSession from "supertokens-web-js-script/recipe/session"; +supertokens.init({ + appInfo: { + apiDomain: "...", + appName: "...", + }, + recipeList: [ + // highlight-start + supertokensEmailVerification.init(), + supertokensSession.init(), + ], +}); +``` + + + + + + + + +:::success +No specific action required here. +::: + + + + + +## Step 3: Checking if the user's email is verified in your APIs + +

If using REQUIRED mode

+ +On the backend, when you initialize the email verification recipe in this mode, the `verifySession` middleware automatically checks if the user's email is verified based on the contents of the session's payload. If the email is not verified, the `verifySession` middleware will return a `403` status code to the client. + +

If using OPTIONAL mode

+ +In this mode, you need to check if the email is verified yourself in the APIs in which you want this constraint. The verification status should already be in the session's payload. + + + + + + +```tsx +import { verifySession } from "supertokens-node/recipe/session/framework/express"; +import express from "express"; +import { SessionRequest } from "supertokens-node/framework/express"; +import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; + +let app = express(); + +app.post( + "/update-blog", + verifySession({ + // highlight-next-line + overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], + }), + async (req: SessionRequest, res) => { + // All validator checks have passed and the user has a verified email address + } +); +``` + + + +```tsx +import Hapi from "@hapi/hapi"; +import { verifySession } from "supertokens-node/recipe/session/framework/hapi"; +import {SessionRequest} from "supertokens-node/framework/hapi"; +import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; + +let server = Hapi.server({ port: 8000 }); + +server.route({ + path: "/update-blog", + method: "post", + options: { + pre: [ + { + method: verifySession({ + // highlight-next-line + overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], + }), + }, + ], + }, + handler: async (req: SessionRequest, res) => { + // All validator checks have passed and the user has a verified email address + } +}) +``` + + + +```tsx +import Fastify from "fastify"; +import { verifySession } from "supertokens-node/recipe/session/framework/fastify"; +import { SessionRequest } from "supertokens-node/framework/fastify"; +import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; + +let fastify = Fastify(); + +fastify.post("/update-blog", { + preHandler: verifySession({ + // highlight-next-line + overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], + }), +}, async (req: SessionRequest, res) => { + // All validator checks have passed and the user has a verified email address +}); +``` + + + + +```tsx +import { verifySession } from "supertokens-node/recipe/session/framework/awsLambda"; +import { SessionEvent } from "supertokens-node/framework/awsLambda"; +import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; + +async function updateBlog(awsEvent: SessionEvent) { + // All validator checks have passed and the user has a verified email address +}; + +exports.handler = verifySession(updateBlog, { + // highlight-next-line + overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] +}); +``` + + + + +```tsx +import KoaRouter from "koa-router"; +import { verifySession } from "supertokens-node/recipe/session/framework/koa"; +import {SessionContext} from "supertokens-node/framework/koa"; +import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; + +let router = new KoaRouter(); + +router.post("/update-blog", verifySession({ + // highlight-next-line + overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] + }), async (ctx: SessionContext, next) => { + // All validator checks have passed and the user has a verified email address +}); +``` + + + + +```tsx +import { inject, intercept } from "@loopback/core"; +import { RestBindings, MiddlewareContext, post, response } from "@loopback/rest"; +import { verifySession } from "supertokens-node/recipe/session/framework/loopback"; +import Session from "supertokens-node/recipe/session"; +import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; + +class SetRole { + constructor(@inject(RestBindings.Http.CONTEXT) private ctx: MiddlewareContext) { } + @post("/update-blog") + @intercept(verifySession({ + // highlight-next-line + overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] + })) + @response(200) + async handler() { + // All validator checks have passed and the user has a verified email address + } +} +``` + + + + +```tsx +import { superTokensNextWrapper } from 'supertokens-node/nextjs' +import { verifySession } from "supertokens-node/recipe/session/framework/express"; +import { SessionRequest } from "supertokens-node/framework/express"; +import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; + +// highlight-start +export default async function setRole(req: SessionRequest, res: any) { + await superTokensNextWrapper( + async (next) => { + await verifySession({ + // highlight-next-line + overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] + })(req, res, next); + }, + req, + res + ) + // All validator checks have passed and the user has a verified email address +} +``` + + + + +```tsx +import SuperTokens from "supertokens-node"; +import { NextResponse, NextRequest } from "next/server"; +import { withSession } from "supertokens-node/nextjs"; +import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; +// @ts-ignore +import { backendConfig } from "@/app/config/backend"; + +SuperTokens.init(backendConfig()); + +export async function POST(request: NextRequest) { + return withSession(request, async (err, session) => { + if (err) { + return NextResponse.json(err, { status: 500 }); + } + // All validator checks have passed and the user has a verified email address + return NextResponse.json({ message: "Your email is verified!" }); + }, + { + // highlight-next-line + overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], + } + ); +} +``` + + + + +```tsx +import { Controller, Post, UseGuards, Request, Response, Session } from "@nestjs/common"; +import { SessionContainer, SessionClaimValidator } from "supertokens-node/recipe/session"; +// @ts-ignore +import { AuthGuard } from './auth/auth.guard'; +import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; + +@Controller() +export class ExampleController { + @Post('example') + @UseGuards(new AuthGuard({ + // highlight-next-line + overrideGlobalClaimValidators: async (globalValidators: SessionClaimValidator[]) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] + })) + async postExample(@Session() session: SessionContainer): Promise { + // All validator checks have passed and the user has a verified email address + return true; + } +} +``` + + + + + + + + +```go +import ( + "net/http" + + "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" + "github.com/supertokens/supertokens-golang/recipe/session" + "github.com/supertokens/supertokens-golang/recipe/session/claims" + "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" + "github.com/supertokens/supertokens-golang/supertokens" +) + +func main() { + _ = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + session.VerifySession(&sessmodels.VerifySessionOptions{ + // highlight-start + OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { + globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) + return globalClaimValidators, nil + }, + // highlight-end + }, exampleAPI).ServeHTTP(rw, r) + }) +} + +func exampleAPI(w http.ResponseWriter, r *http.Request) { + // TODO: session is verified and all validators have passed.. +} +``` + + + + +```go +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" + "github.com/supertokens/supertokens-golang/recipe/session" + "github.com/supertokens/supertokens-golang/recipe/session/claims" + "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" + "github.com/supertokens/supertokens-golang/supertokens" +) + +func main() { + router := gin.New() + + // Wrap the API handler in session.VerifySession + router.POST("/likecomment", verifySession(&sessmodels.VerifySessionOptions{ + // highlight-start + OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { + globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) + return globalClaimValidators, nil + }, + // highlight-end + }), exampleAPI) +} + +// This is a function that wraps the supertokens verification function +// to work the gin +func verifySession(options *sessmodels.VerifySessionOptions) gin.HandlerFunc { + return func(c *gin.Context) { + session.VerifySession(options, func(rw http.ResponseWriter, r *http.Request) { + c.Request = c.Request.WithContext(r.Context()) + c.Next() + })(c.Writer, c.Request) + // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly + c.Abort() + } +} + +func exampleAPI(c *gin.Context) { + // TODO: session is verified and all claim validators pass. +} +``` + + + + +```go +import ( + "net/http" + + "github.com/go-chi/chi" + "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" + "github.com/supertokens/supertokens-golang/recipe/session" + "github.com/supertokens/supertokens-golang/recipe/session/claims" + "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" + "github.com/supertokens/supertokens-golang/supertokens" +) + +func main() { + r := chi.NewRouter() + + // Wrap the API handler in session.VerifySession + r.Post("/likecomment", session.VerifySession(&sessmodels.VerifySessionOptions{ + // highlight-start + OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { + globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) + return globalClaimValidators, nil + }, + // highlight-end + }, exampleAPI)) +} + +func exampleAPI(w http.ResponseWriter, r *http.Request) { + // TODO: session is verified and all claim validators pass. +} +``` + + + + +```go +import ( + "net/http" + + "github.com/gorilla/mux" + "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" + "github.com/supertokens/supertokens-golang/recipe/session" + "github.com/supertokens/supertokens-golang/recipe/session/claims" + "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" + "github.com/supertokens/supertokens-golang/supertokens" +) + +func main() { + router := mux.NewRouter() + + // Wrap the API handler in session.VerifySession + router.HandleFunc("/likecomment", session.VerifySession(&sessmodels.VerifySessionOptions{ + // highlight-start + OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { + globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) + return globalClaimValidators, nil + }, + // highlight-end + }, exampleAPI)).Methods(http.MethodPost) +} + +func exampleAPI(w http.ResponseWriter, r *http.Request) { + // TODO: session is verified and all claim validators pass. +} +``` + + + + + + + + + +```python +from supertokens_python.recipe.session.framework.fastapi import verify_session +from supertokens_python.recipe.emailverification import EmailVerificationClaim +from supertokens_python.recipe.session import SessionContainer +from fastapi import Depends + +@app.post('/like_comment') # type: ignore +async def like_comment(session: SessionContainer = Depends( + verify_session( + # highlight-start + # We add the EmailVerificationClaim's is_verified validator + override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \ + [EmailVerificationClaim.validators.is_verified()] + # highlight-end + ) +)): + # All validator checks have passed and the user has a verified email address + pass +``` + + + + +```python +from supertokens_python.recipe.session.framework.flask import verify_session +from supertokens_python.recipe.emailverification import EmailVerificationClaim + +@app.route('/update-jwt', methods=['POST']) # type: ignore +@verify_session( + # highlight-start + # We add the EmailVerificationClaim's is_verified validator + override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \ + [EmailVerificationClaim.validators.is_verified()] + # highlight-end +) +def like_comment(): + # All validator checks have passed and the user has a verified email address + pass +``` + + + + +```python +from supertokens_python.recipe.session.framework.django.asyncio import verify_session +from django.http import HttpRequest +from supertokens_python.recipe.emailverification import EmailVerificationClaim + +@verify_session( + # highlight-start + # We add the EmailVerificationClaim's is_verified validator + override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \ + [EmailVerificationClaim.validators.is_verified()] + # highlight-end +) +async def like_comment(request: HttpRequest): + # All validator checks have passed and the user has a verified email address + pass +``` + + + + + + + +We add the SDK's `EmailVerificationClaim` validator to the `verifySession` middleware call as shown above, and that will only allow access if the email is verified, else it will return `403` to the frontend. + + +import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" + +## Step 4: Protecting frontend routes + + + + + + + +```tsx +import Session from "supertokens-web-js/recipe/session"; +import { EmailVerificationClaim } from "supertokens-web-js/recipe/emailverification"; + +async function shouldLoadRoute(): Promise { + if (await Session.doesSessionExist()) { + // highlight-start + let validationErrors = await Session.validateClaims(); + + if (validationErrors.length === 0) { + // user has verified their email address + return true; + } else { + for (const err of validationErrors) { + if (err.id === EmailVerificationClaim.id) { + // email is not verified + } + } + } + // highlight-end + } + // a session does not exist, or email is not verified + return false +} +``` + + + + +```tsx +import supertokensSession from "supertokens-web-js-script/recipe/session"; +import supertokensEmailVerification from "supertokens-web-js-script/recipe/emailverification"; +async function shouldLoadRoute(): Promise { + if (await supertokensSession.doesSessionExist()) { + // highlight-start + let validationErrors = await supertokensSession.validateClaims(); + + if (validationErrors.length === 0) { + // user has verified their email address + return true; + } else { + for (const err of validationErrors) { + if (err.id === supertokensEmailVerification.EmailVerificationClaim.id) { + // email is not verified + } + } + } + // highlight-end + } + // a session does not exist, or email is not verified + return false +} +``` + + + + +In your protected routes, you need to first check if a session exists, and then call the `Session.validateClaims` function as shown above. This function inspects the session's contents and runs claim validators on them. If a claim validator fails, it will be reflected in the `validationErrors` variable. The `EmailVerificationClaim` validator will be automatically checked by this function since you have initialized the email verification recipe. + +
Handling 403 responses on the frontend + + + +If your frontend queries a protected API on your backend and it fails with a 403, you can call the `validateClaims` function and loop through the errors to know which claim has failed: + +```tsx +import axios from "axios"; +import Session from "supertokens-web-js/recipe/session"; +import { EmailVerificationClaim, sendVerificationEmail } from "supertokens-web-js/recipe/emailverification"; + +async function callProtectedRoute() { + try { + let response = await axios.get("^{form_apiDomain}/protectedroute"); + } catch (error) { + // highlight-start + if (axios.isAxiosError(error) && error.response?.status === 403) { + let validationErrors = await Session.validateClaims(); + for (let err of validationErrors) { + if (err.id === EmailVerificationClaim.id) { + // email verification claim check failed + // We call the sendEmail function defined in the next section to send the verification email. + // await sendEmail(); + } else { + // some other claim check failed (from the global validators list) + } + } + // highlight-end + + } + } +} +``` + + + +
+ +
+ + + + + + + +```tsx +import SuperTokens from 'supertokens-react-native'; + +async function checkIfEmailIsVerified() { + if (await SuperTokens.doesSessionExist()) { + + // highlight-start + let isVerified: boolean = (await SuperTokens.getAccessTokenPayloadSecurely())["st-ev"].v; + + if (isVerified) { + // TODO.. + } else { + // TODO.. + } + // highlight-end + } +} +``` + + + + + +```kotlin +import android.app.Application +import com.supertokens.session.SuperTokens +import org.json.JSONObject + +class MainApplication: Application() { + fun checkIfEmailIsVerified() { + val accessTokenPayload: JSONObject = SuperTokens.getAccessTokenPayloadSecurely(this); + val isVerified: Boolean = (accessTokenPayload.get("st-ev") as JSONObject).get("v") as Boolean + if (isVerified) { + // TODO.. + } else { + // TODO.. + } + } +} +``` + + + + + +```swift +import UIKit +import SuperTokensIOS + +fileprivate class ViewController: UIViewController { + func checkIfEmailIsVerified() { + if let accessTokenPayload: [String: Any] = try? SuperTokens.getAccessTokenPayloadSecurely(), let emailVerificationObject: [String: Any] = accessTokenPayload["st-ev"] as? [String: Any], let isVerified: Bool = emailVerificationObject["v"] as? Bool { + if isVerified { + // Email is verified + } else { + // Email is not verified + } + } + } +} +``` + + + + + +```dart +import 'package:supertokens_flutter/supertokens.dart'; + +Future checkIfEmailIsVerified() async { + var accessTokenPayload = await SuperTokens.getAccessTokenPayloadSecurely(); + + if (accessTokenPayload.containsKey("st-ev")) { + Map emailVerificationObject = accessTokenPayload["st-ev"]; + + if (emailVerificationObject.containsKey("v")) { + bool isVerified = emailVerificationObject["v"]; + + if (isVerified) { + // Email is verified + } else { + // Email is not verified + } + } + } +} +``` + + + + + +
Handling 403 responses on the frontend + +If your frontend queries a protected API on your backend and it fails with a 403, you can check the value of the `st-ev` claim in the access token payload. If it is set to false you can send the verification email + +
+ +
+
+ +import AppInfoForm from "/src/components/appInfoForm" + +## Step 5: Sending the email verification email + +When the email verification validators fail, or post sign up, you want to redirect the user to a screen telling them that a verification email has been sent to them. On this screen, you should call the following API + + + + + + + +```tsx +import { sendVerificationEmail } from "supertokens-web-js/recipe/emailverification"; + +async function sendEmail() { + try { + let response = await sendVerificationEmail(); + if (response.status === "EMAIL_ALREADY_VERIFIED_ERROR") { + // This can happen if the info about email verification in the session was outdated. + // Redirect the user to the home page + window.location.assign("/home"); + } else { + // email was sent successfully. + window.alert("Please check your email and click the link in it") + } + } catch (err: any) { + if (err.isSuperTokensGeneralError === true) { + // this may be a custom error message sent from the API by you. + window.alert(err.message); + } else { + window.alert("Oops! Something went wrong."); + } + } +} +``` + + + + +```tsx +import supertokensEmailVerification from "supertokens-web-js/recipe/emailverification"; + +async function sendEmail() { + try { + let response = await supertokensEmailVerification.sendVerificationEmail(); + if (response.status === "EMAIL_ALREADY_VERIFIED_ERROR") { + // This can happen if the info about email verification in the session was outdated. + // Redirect the user to the home page + window.location.assign("/home"); + } else { + // email was sent successfully. + window.alert("Please check your email and click the link in it") + } + } catch (err: any) { + if (err.isSuperTokensGeneralError === true) { + // this may be a custom error message sent from the API by you. + window.alert(err.message); + } else { + window.alert("Oops! Something went wrong."); + } + } +} +``` + + + + + + + + + + +You should create a new screen on your app that asks the user to enter their email to which an email will be sent. This screen should ideally be linked to from the sign in form. + +Once the user has enters their email, you can call the following API to send an email verification email to that user: + +```bash +curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/user/email/verify/token' \ +--header 'Authorization: Bearer ...' +``` + + + +The response body from the API call has a `status` property in it: +- `status: "OK"`: An email was sent to the user successfully. +- `status: "EMAIL_ALREADY_VERIFIED_ERROR"`: This status can be returned if the info about email verification in the session was outdated. Redirect the user to the home page. +- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. + + + +You do not need to add the tenant ID to the path here because the backend fetches the tenantId of the user from the session token. + + + + + + +:::note +The API for sending an email verification email requires an active session. If you are using our frontend SDKs, then the session tokens should automatically get attached to the request. +::: + +### Changing the email verification link domain / path +By default, the email verification link will point to the `websiteDomain` that is configured on the backend, on the `/auth/verify-email` route (where `/auth` is the default value of `websiteBasePath`). + +If you want to change this to a different path, a different domain, or deep link it to your mobile / desktop app, then you can do so on the backend in the following way: + + + + +```tsx +import SuperTokens from "supertokens-node"; +import EmailVerification from "supertokens-node/recipe/emailverification"; + +SuperTokens.init({ + supertokens: { + connectionURI: "...", + }, + appInfo: { + apiDomain: "...", + appName: "...", + websiteDomain: "..." + }, + recipeList: [ + EmailVerification.init({ + mode: "OPTIONAL", + // highlight-start + emailDelivery: { + override: (originalImplementation) => { + return { + ...originalImplementation, + sendEmail(input) { + return originalImplementation.sendEmail({ + ...input, + emailVerifyLink: input.emailVerifyLink.replace( + // This is: `${websiteDomain}${websiteBasePath}/verify-email` + "http://localhost:3000/auth/verify-email", + "http://localhost:3000/your/path" + ) + } + ) + }, + } + } + } + // highlight-end + }) + ] +}); +``` + + + +```go +import ( + "strings" + + "github.com/supertokens/supertokens-golang/ingredients/emaildelivery" + "github.com/supertokens/supertokens-golang/recipe/emailverification" + "github.com/supertokens/supertokens-golang/recipe/emailverification/evmodels" + "github.com/supertokens/supertokens-golang/supertokens" +) + +func main() { + supertokens.Init(supertokens.TypeInput{ + RecipeList: []supertokens.Recipe{ + emailverification.Init(evmodels.TypeInput{ + Mode: evmodels.ModeOptional, + // highlight-start + EmailDelivery: &emaildelivery.TypeInput{ + Override: func(originalImplementation emaildelivery.EmailDeliveryInterface) emaildelivery.EmailDeliveryInterface { + ogSendEmail := *originalImplementation.SendEmail + + (*originalImplementation.SendEmail) = func(input emaildelivery.EmailType, userContext supertokens.UserContext) error { + // This is: `${websiteDomain}${websiteBasePath}/verify-email` + input.EmailVerification.EmailVerifyLink = strings.Replace( + input.EmailVerification.EmailVerifyLink, + "http://localhost:3000/auth/verify-email", + "http://localhost:3000/your/path", 1, + ) + return ogSendEmail(input, userContext) + } + return originalImplementation + }, + }, + // highlight-end + }), + }, + }) +} +``` + + + +```python +from supertokens_python import init, InputAppInfo +from supertokens_python.recipe import emailverification +from supertokens_python.ingredients.emaildelivery.types import EmailDeliveryConfig +from supertokens_python.recipe.emailverification.types import EmailDeliveryOverrideInput, EmailTemplateVars +from typing import Dict, Any + + +def custom_email_delivery(original_implementation: EmailDeliveryOverrideInput) -> EmailDeliveryOverrideInput: + original_send_email = original_implementation.send_email + + # highlight-start + async def send_email(template_vars: EmailTemplateVars, user_context: Dict[str, Any]) -> None: + + # This is: `${websiteDomain}${websiteBasePath}/verify-email` + template_vars.email_verify_link = template_vars.email_verify_link.replace( + "http://localhost:3000/auth/verify-email", "http://localhost:3000/your/path") + + return await original_send_email(template_vars, user_context) + # highlight-end + + original_implementation.send_email = send_email + return original_implementation + + +init( + app_info=InputAppInfo( + api_domain="...", app_name="...", website_domain="..."), + framework='...', # type: ignore + recipe_list=[ + emailverification.init( + mode="OPTIONAL", + # highlight-next-line + email_delivery=EmailDeliveryConfig(override=custom_email_delivery)) + ] +) +``` + + + + + + + +For a multi tenant setup, the input to the `sendEmail` function will also contain the `tenantId`. You can use this to determine the correct value to set for the websiteDomain in the generated link. + + + +## Step 6: Verifying the email post link clicked + +Once the user clicks the email verification link, and it opens your app, you can call the following function which will automatically extract the token and tenantId (if using a multi tenant setup) from the link and call the token verification API. + + + + + + + +```tsx +import { verifyEmail } from "supertokens-web-js/recipe/emailverification"; + +async function consumeVerificationCode() { + try { + let response = await verifyEmail(); + if (response.status === "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR") { + // This can happen if the verification code is expired or invalid. + // You should ask the user to retry + window.alert("Oops! Seems like the verification link expired. Please try again") + window.location.assign("/auth/verify-email") // back to the email sending screen. + } else { + // email was verified successfully. + window.location.assign("/home") + } + } catch (err: any) { + if (err.isSuperTokensGeneralError === true) { + // this may be a custom error message sent from the API by you. + window.alert(err.message); + } else { + window.alert("Oops! Something went wrong."); + } + } +} +``` + + + + +```tsx +import supertokensEmailVerification from "supertokens-web-js/recipe/emailverification"; + +async function consumeVerificationCode() { + try { + let response = await supertokensEmailVerification.verifyEmail(); + if (response.status === "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR") { + // This can happen if the verification code is expired or invalid. + // You should ask the user to retry + window.alert("Oops! Seems like the verification link expired. Please try again") + window.location.assign("/auth/verify-email") // back to the email sending screen. + } else { + // email was verified successfully. + window.location.assign("/home") + } + } catch (err: any) { + if (err.isSuperTokensGeneralError === true) { + // this may be a custom error message sent from the API by you. + window.alert(err.message); + } else { + window.alert("Oops! Something went wrong."); + } + } +} +``` + + + + + + + + + + +Once the user clicks the email verification link, and it opens as a deep link into your mobile app, you can extract the token from the link and call the verification API as shown below: + +```bash +curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/user/email/verify' \ +--header 'Content-Type: application/json; charset=utf-8' \ +--data-raw '{ + "method": "token", + "token": "ZTRiOTBjNz...jI5MTZlODkxw" +}' +``` + + + + +For a multi tenancy setup, the `` value can be fetched from `tenantId` query parameter from the email verification link. If it's not there in the link, you can use the value `"public"` (which is the default tenant). + + + + + + +The response body from the API call has a `status` property in it: +- `status: "OK"`: Email verification was successful. +- `status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR"`: This can happen if the verification code is expired or invalid. You should ask the user to retry. +- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. + + + + +:::caution +- This API doesn't require an active session to succeed. +- If you are calling the above API on page load, there is an edge case in which email clients might open the verification link in the email (for scanning purposes) and consume the token in the URL. This would lead to issues in which an attacker could sign up using someone else's email and end up with a veriifed status! + + To prevent this, on page load, you should check if a session exists, and if it does, only then call the above API. If a session does not exist, you should first show a button, which when clicked would call the above API (email clients won't automatically click on this button). The button text could be something like "Click here to verify your email". +::: + + +## See also + +- [Post email verification action](../common-customizations/email-verification/handling-email-verification-success) +- [Change email verification link's lifetime](../common-customizations/email-verification/changing-token-lifetime) +- [Customise email template or email delivery method](../email-delivery/about) +- [Manually changing email verification status for a user](../common-customizations/email-verification/changing-email-verification-status) +- [Generating email verification links manually](../common-customizations/email-verification/generate-link-manually) +- 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) in `REQUIRED` mode. +
diff --git a/v2/emailpassword/common-customizations/multi-tenancy/new-tenant-config.mdx b/v2/emailpassword/common-customizations/multi-tenancy/new-tenant-config.mdx index 737680f53..ed898c478 100644 --- a/v2/emailpassword/common-customizations/multi-tenancy/new-tenant-config.mdx +++ b/v2/emailpassword/common-customizations/multi-tenancy/new-tenant-config.mdx @@ -26,158 +26,6 @@ The configuration mapped to each tenant contains information about which login m - - - - - -```tsx -import Multitenancy from "supertokens-node/recipe/multitenancy"; - -async function createNewTenant() { - - // highlight-start - let resp = await Multitenancy.createOrUpdateTenant("customer1", { - firstFactors: ["emailpassword"] - }); - // highlight-end - - if (resp.createdNew) { - // new tenant was created - } else { - // existing tenant's config was modified. - } -} -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/multitenancy" - "github.com/supertokens/supertokens-golang/recipe/multitenancy/multitenancymodels" -) - -func main() { - tenantId := "customer1" - emailPasswordEnabled := true - - // highlight-start - resp, err := multitenancy.CreateOrUpdateTenant(tenantId, multitenancymodels.TenantConfig{ - EmailPasswordEnabled: &emailPasswordEnabled, - }) - // highlight-end - - if err != nil { - // handle error - } - if resp.OK.CreatedNew { - // new tenant was created - } else { - // existing tenant's config was modified. - } -} -``` - - - - - - - -```python -from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate - -async def some_func(): - tenant_id = "customer1" - result = await create_or_update_tenant(tenant_id, TenantConfigCreateOrUpdate( - first_factors=["emailpassword"] - )) - - if result.status != "OK": - print("handle error") - elif result.created_new: - print("new tenant created") - else: - print("existing tenant's config was modified.") -``` - - - - - -```python -from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate - -tenant_id = "customer1" -result = create_or_update_tenant(tenant_id, TenantConfigCreateOrUpdate( - first_factors=["emailpassword"] -)) - -if result.status != "OK": - print("handle error") -elif result.created_new: - print("new tenant created") -else: - print("existing tenant's config was modified.") -``` - - - - - - - - - - - - - - -```bash -curl --location --request PUT '^{coreInjector_uri_without_quotes}/recipe/multitenancy/tenant/v2' \ ---header 'api-key: ^{coreInjector_api_key_without_quotes}' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "tenantId": "customer1", - "firstFactors": ["emailpassword"] -}' -``` - - - - - -```bash -curl --location --request PUT '^{coreInjector_uri_without_quotes}/recipe/multitenancy/tenant' \ ---header 'api-key: ^{coreInjector_api_key_without_quotes}' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "tenantId": "customer1", - "emailPasswordEnabled": true -}' -``` - - - - - - - - - - - -Create Tenant - - - - - diff --git a/v2/emailpassword/common-customizations/multi-tenancy/overview.mdx b/v2/emailpassword/common-customizations/multi-tenancy/overview.mdx index 44d24d00a..bce8db614 100644 --- a/v2/emailpassword/common-customizations/multi-tenancy/overview.mdx +++ b/v2/emailpassword/common-customizations/multi-tenancy/overview.mdx @@ -11,13 +11,6 @@ import TabItem from '@theme/TabItem'; - - - -# Multitenant login - -Multitenant login is a feature that lets you customize the login experience for each of your customers. For example, a customer `customer1` hosted on `customer1.yourdomain.com` can have login with email password (using this recipe), and another customer `customer2` hosted on `customer2.yourdomain.com` can have login with `Active Directory` and `Facebook` (using the thirdparty recipe). - ## Features diff --git a/v2/emailpassword/common-customizations/sessions/protecting-frontend-routes.mdx b/v2/emailpassword/common-customizations/sessions/protecting-frontend-routes.mdx index 9e5719247..7a0f1c394 100644 --- a/v2/emailpassword/common-customizations/sessions/protecting-frontend-routes.mdx +++ b/v2/emailpassword/common-customizations/sessions/protecting-frontend-routes.mdx @@ -291,133 +291,6 @@ async function shouldLoadRoute(): Promise { - - - - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```tsx -import Session from 'supertokens-web-js/recipe/session'; - -async function doesSessionExist() { - if (await Session.doesSessionExist()) { - // user is logged in - } else { - // user has not logged in yet - } -} -``` - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```tsx -import Session from 'supertokens-web-js-script/recipe/session'; -async function doesSessionExist() { - if (await Session.doesSessionExist()) { - // user is logged in - } else { - // user has not logged in yet - } -} -``` - - - - - - - - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```tsx -import SuperTokens from 'supertokens-react-native'; - -async function doesSessionExist() { - if (await SuperTokens.doesSessionExist()) { - // user is logged in - } else { - // user has not logged in yet - } -} -``` - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```kotlin -import android.app.Application -import com.supertokens.session.SuperTokens - -class MainApplication: Application() { - fun doesSessionExist() { - if (SuperTokens.doesSessionExist(this.applicationContext)) { - // user is logged in - } else { - // user has not logged in yet - } - } -} -``` - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```swift -import UIKit -import SuperTokensIOS - -fileprivate class ViewController: UIViewController { - func doesSessionExist() { - if SuperTokens.doesSessionExist() { - // User is logged in - } else { - // User is not logged in - } - } -} -``` - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```dart -import 'package:supertokens_flutter/supertokens.dart'; - -Future doesSessionExist() async { - return await SuperTokens.doesSessionExist(); -} -``` - - - - - - - - - ## Verifying the claims of a session diff --git a/v2/emailpassword/custom-ui/email-password-login.mdx b/v2/emailpassword/custom-ui/email-password-login.mdx index c1b85b51b..94c342c09 100644 --- a/v2/emailpassword/custom-ui/email-password-login.mdx +++ b/v2/emailpassword/custom-ui/email-password-login.mdx @@ -4,425 +4,8 @@ title: Email Password login hide_title: true --- - - - +import Redirector from '/src/components/Redirector'; -import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" -import TabItem from '@theme/TabItem'; -import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" -import AppInfoForm from "/src/components/appInfoForm" -# Email Password login + -## Sign up form - - - - -Call the following function when the user clicks on the sign up button. - - - - -```tsx -import { signUp } from "supertokens-web-js/recipe/emailpassword"; - -async function signUpClicked(email: string, password: string) { - try { - let response = await signUp({ - formFields: [{ - id: "email", - value: email - }, { - id: "password", - value: password - }] - }) - - if (response.status === "FIELD_ERROR") { - // one of the input formFields failed validation - response.formFields.forEach(formField => { - if (formField.id === "email") { - // Email validation failed (for example incorrect email syntax), - // or the email is not unique. - window.alert(formField.error) - } else if (formField.id === "password") { - // Password validation failed. - // Maybe it didn't match the password strength - window.alert(formField.error) - } - }) - } else if (response.status === "SIGN_UP_NOT_ALLOWED") { - // the reason string is a user friendly message - // about what went wrong. It can also contain a support code which users - // can tell you so you know why their sign up was not allowed. - window.alert(response.reason) - } else { - // sign up successful. The session tokens are automatically handled by - // the frontend SDK. - window.location.href = "/homepage" - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - -```tsx -import supertokensEmailPassword from "supertokens-web-js-script/recipe/emailpassword"; -async function signUpClicked(email: string, password: string) { - try { - let response = await supertokensEmailPassword.signUp({ - formFields: [{ - id: "email", - value: email - }, { - id: "password", - value: password - }] - }) - - if (response.status === "FIELD_ERROR") { - // one of the input formFields failed validation - response.formFields.forEach(formField => { - if (formField.id === "email") { - // Email validation failed (for example incorrect email syntax), - // or the email is not unique. - window.alert(formField.error) - } else if (formField.id === "password") { - // Password validation failed. - // Maybe it didn't match the password strength - window.alert(formField.error) - } - }) - } else if (response.status === "SIGN_UP_NOT_ALLOWED") { - // the reason string is a user friendly message - // about what went wrong. It can also contain a support code which users - // can tell you so you know why their sign in was not allowed. - window.alert(response.reason) - } else { - // sign up successful. The session tokens are automatically handled by - // the frontend SDK. - window.location.href = "/homepage" - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - - - - - - - -Call the follwing API when the user clicks on the sign up button (the command below can be tried on your terminal). - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signup' \ ---header 'Content-Type: application/json; charset=utf-8' \ ---data-raw '{ - "formFields": [{ - "id": "email", - "value": "john@example.com" - }, { - "id": "password", - "value": "somePassword123" - }] -}' -``` - - - -The response body from the API call has a `status` property in it: -- `status: "OK"`: User creation was successful. The response also contains more information about the user, for example their user ID. -- `status: "FIELD_ERROR"`: One of the form field inputs failed validation. The response body contains information about which form field input based on the `id`: - - The email could fail validation if it's syntactically not an email, of it it's not unique. - - The password could fail validation if it's not string enough (as defined by the backend password validator). - - Either way, you want to show the user an error next to the input form field. -- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. -- `status: "SIGN_UP_NOT_ALLOWED"`: This can happen during automatic account linking or during MFA. The `reason` prop that's in the response body contains a support code using which you can see why the sign up was not allowed. - - - - - -The `formFields` input is a key-value array. You must provide it an `email` and a `password` value at a minimum. If you want to provide additional items, for example the user's name or age, you can append it to the array like so: - -```json -{ - "formFields": [{ - "id": "email", - "value": "john@example.com" - }, { - "id": "password", - "value": "somePassword123" - }, { - "id": "name", - "value": "John Doe" - }] -} -``` - -On the backend, the `formFields` array will be available to you for consumption. - -:::note -On success, the backend will send back session tokens as part of the response headers which will be automatically handled by our frontend SDK for you. -::: - -### Checking if email is unique - -As a part of the sign up form, you may want to explicitly check that the entered email is unique. Whilst this is already done via the sign up API call, it may be a better UX to warn the user about a non unique email right after they finish typing it. - - - - - - - -```tsx -import { doesEmailExist } from "supertokens-web-js/recipe/emailpassword"; - -async function checkEmail(email: string) { - try { - let response = await doesEmailExist({ - email - }); - - if (response.doesExist) { - window.alert("Email already exists. Please sign in instead") - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - -```tsx -import supertokensEmailPassword from "supertokens-web-js-script/recipe/emailpassword"; -async function checkEmail(email: string) { - try { - let response = await supertokensEmailPassword.doesEmailExist({ - email - }); - - if (response.doesExist) { - window.alert("Email already exists. Please sign in instead") - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - - - - - - - -```bash -curl --location --request GET '^{form_apiDomain}^{form_apiBasePath}/emailpassword/email/exists?email=john@example.com' -``` - - - -The response body from the API call has a `status` property in it: -- `status: "OK"`: The response will also contain a `exists` boolean which will be `true` if the input email already belongs to an email password user. -- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. - - - - - -## Sign in form - - - - -Call the follwing function when the user clicks on the sign in button. - - - - -```tsx -import { signIn } from "supertokens-web-js/recipe/emailpassword"; - -async function signInClicked(email: string, password: string) { - try { - let response = await signIn({ - formFields: [{ - id: "email", - value: email - }, { - id: "password", - value: password - }] - }) - - if (response.status === "FIELD_ERROR") { - response.formFields.forEach(formField => { - if (formField.id === "email") { - // Email validation failed (for example incorrect email syntax). - window.alert(formField.error) - } - }) - } else if (response.status === "WRONG_CREDENTIALS_ERROR") { - window.alert("Email password combination is incorrect.") - } else if (response.status === "SIGN_IN_NOT_ALLOWED") { - // the reason string is a user friendly message - // about what went wrong. It can also contain a support code which users - // can tell you so you know why their sign in was not allowed. - window.alert(response.reason) - } else { - // sign in successful. The session tokens are automatically handled by - // the frontend SDK. - window.location.href = "/homepage" - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - -```tsx -import supertokensEmailPassword from "supertokens-web-js-script/recipe/emailpassword"; -async function signInClicked(email: string, password: string) { - try { - let response = await supertokensEmailPassword.signIn({ - formFields: [{ - id: "email", - value: email - }, { - id: "password", - value: password - }] - }) - - if (response.status === "FIELD_ERROR") { - // one of the input formFields failed validation - response.formFields.forEach(formField => { - if (formField.id === "email") { - // Email validation failed (for example incorrect email syntax). - window.alert(formField.error) - } - }) - } else if (response.status === "WRONG_CREDENTIALS_ERROR") { - window.alert("Email password combination is incorrect.") - } else if (response.status === "SIGN_IN_NOT_ALLOWED") { - // the reason string is a user friendly message - // about what went wrong. It can also contain a support code which users - // can tell you so you know why their sign in was not allowed. - window.alert(response.reason) - } else { - // sign in successful. The session tokens are automatically handled by - // the frontend SDK. - window.location.href = "/homepage" - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - - - - - - - -Call the follwing API when the user clicks on the sign in button (the command below can be tried on your terminal). - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signin' \ ---header 'Content-Type: application/json; charset=utf-8' \ ---data-raw '{ - "formFields": [{ - "id": "email", - "value": "john@example.com" - }, { - "id": "password", - "value": "somePassword123" - }] -}' -``` - - - -The response body from the API call has a `status` property in it: -- `status: "OK"`: User sign in was successful. The response also contains more information about the user, for example their user ID. -- `status: "WRONG_CREDENTIALS_ERROR"`: The input email and password combination is incorrect. -- `status: "FIELD_ERROR"`: This indicates that the input email did not pass the backend validation - probably because it's syntactically not an email. You want to show the user an error next to the email input form field. -- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. -- `status: "SIGN_IN_NOT_ALLOWED"`: This can happen during automatic account linking or during MFA. The `reason` prop that's in the response body contains a support code using which you can see why the sign in was not allowed. - - - - - -:::important -On success, the backend will send back session tokens as part of the response headers which will be automatically handled by our frontend SDK for you. -::: - - - -## See also - -- [Post sign in action](../common-customizations/handling-signin-success) -- [Post sign up action](../common-customizations/handling-signup-success) -- [Adding extra form fields in sign up](../common-customizations/signup-form/adding-fields) -- [Changing fields validation logic on the backend](../common-customizations/signup-form/field-validators) -- [Password hashing algorithms](../common-customizations/password-hashing/about) -- [Disabling sign up on the backend](../advanced-customizations/apis-override/disabling) -- [Customising user ID format](../common-customizations/userid-format) diff --git a/v2/emailpassword/custom-ui/enable-email-verification.mdx b/v2/emailpassword/custom-ui/enable-email-verification.mdx index addfb0e50..4f3ade3f4 100644 --- a/v2/emailpassword/custom-ui/enable-email-verification.mdx +++ b/v2/emailpassword/custom-ui/enable-email-verification.mdx @@ -4,1234 +4,8 @@ title: Enable email verification hide_title: true --- -# Enable email verification +import Redirector from '/src/components/Redirector'; - - - -import CustomAdmonition from "/src/components/customAdmonition" -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"; + - -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 - - - - -```tsx -import SuperTokens from "supertokens-node"; -import EmailVerification from "supertokens-node/recipe/emailverification"; -import Session from "supertokens-node/recipe/session"; - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // highlight-start - EmailVerification.init({ - mode: "REQUIRED", // or "OPTIONAL" - }), - // highlight-end - Session.init(), - ], -}); -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/emailverification" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evmodels" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - supertokens.Init(supertokens.TypeInput{ - RecipeList: []supertokens.Recipe{ - // highlight-start - emailverification.Init(evmodels.TypeInput{ - Mode: evmodels.ModeRequired, // or evmodels.ModeOptional - }), - // highlight-end - session.Init(&sessmodels.TypeInput{}), - }, - }) -} -``` - - - - -```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import session -from supertokens_python.recipe import emailverification - -init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore - recipe_list=[ - # highlight-start - emailverification.init(mode='REQUIRED'), # or 'OPTIONAL' - # highlight-end - session.init() - ] -) -``` - - - - - - - - - - -import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" -import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" -import WebJsInjector from "/src/components/webJsInjector" - -## Step 2: Frontend setup - - - - - - - -```tsx -import SuperTokens from "supertokens-web-js"; -import EmailVerification from "supertokens-web-js/recipe/emailverification"; -import Session from "supertokens-web-js/recipe/session"; - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - }, - recipeList: [ - // highlight-start - EmailVerification.init(), - Session.init(), - ], -}); -``` - - - - -Add the following ` -``` - - - -Then call the `supertokensEmailVerification.init` function as shown below - -```tsx -import supertokens from "supertokens-web-js-script"; -import supertokensEmailVerification from "supertokens-web-js-script/recipe/emailverification"; -import supertokensSession from "supertokens-web-js-script/recipe/session"; -supertokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - }, - recipeList: [ - // highlight-start - supertokensEmailVerification.init(), - supertokensSession.init(), - ], -}); -``` - - - - - - - - -:::success -No specific action required here. -::: - - - - - - - - - - -## Step 3: Checking if the user's email is verified in your APIs - -

If using REQUIRED mode

- -On the backend, when you initialize the email verification recipe in this mode, the `verifySession` middleware automatically checks if the user's email is verified based on the contents of the session's payload. If the email is not verified, the `verifySession` middleware will return a `403` status code to the client. - -

If using OPTIONAL mode

- -In this mode, you need to check if the email is verified yourself in the APIs in which you want this constraint. The verification status should already be in the session's payload. - - - - - - -```tsx -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import express from "express"; -import { SessionRequest } from "supertokens-node/framework/express"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let app = express(); - -app.post( - "/update-blog", - verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - }), - async (req: SessionRequest, res) => { - // All validator checks have passed and the user has a verified email address - } -); -``` - - - -```tsx -import Hapi from "@hapi/hapi"; -import { verifySession } from "supertokens-node/recipe/session/framework/hapi"; -import {SessionRequest} from "supertokens-node/framework/hapi"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let server = Hapi.server({ port: 8000 }); - -server.route({ - path: "/update-blog", - method: "post", - options: { - pre: [ - { - method: verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - }), - }, - ], - }, - handler: async (req: SessionRequest, res) => { - // All validator checks have passed and the user has a verified email address - } -}) -``` - - - -```tsx -import Fastify from "fastify"; -import { verifySession } from "supertokens-node/recipe/session/framework/fastify"; -import { SessionRequest } from "supertokens-node/framework/fastify"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let fastify = Fastify(); - -fastify.post("/update-blog", { - preHandler: verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - }), -}, async (req: SessionRequest, res) => { - // All validator checks have passed and the user has a verified email address -}); -``` - - - - -```tsx -import { verifySession } from "supertokens-node/recipe/session/framework/awsLambda"; -import { SessionEvent } from "supertokens-node/framework/awsLambda"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -async function updateBlog(awsEvent: SessionEvent) { - // All validator checks have passed and the user has a verified email address -}; - -exports.handler = verifySession(updateBlog, { - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] -}); -``` - - - - -```tsx -import KoaRouter from "koa-router"; -import { verifySession } from "supertokens-node/recipe/session/framework/koa"; -import {SessionContext} from "supertokens-node/framework/koa"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let router = new KoaRouter(); - -router.post("/update-blog", verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - }), async (ctx: SessionContext, next) => { - // All validator checks have passed and the user has a verified email address -}); -``` - - - - -```tsx -import { inject, intercept } from "@loopback/core"; -import { RestBindings, MiddlewareContext, post, response } from "@loopback/rest"; -import { verifySession } from "supertokens-node/recipe/session/framework/loopback"; -import Session from "supertokens-node/recipe/session"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -class SetRole { - constructor(@inject(RestBindings.Http.CONTEXT) private ctx: MiddlewareContext) { } - @post("/update-blog") - @intercept(verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - })) - @response(200) - async handler() { - // All validator checks have passed and the user has a verified email address - } -} -``` - - - - -```tsx -import { superTokensNextWrapper } from 'supertokens-node/nextjs' -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import { SessionRequest } from "supertokens-node/framework/express"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -// highlight-start -export default async function setRole(req: SessionRequest, res: any) { - await superTokensNextWrapper( - async (next) => { - await verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - })(req, res, next); - }, - req, - res - ) - // All validator checks have passed and the user has a verified email address -} -``` - - - - -```tsx -import SuperTokens from "supertokens-node"; -import { NextResponse, NextRequest } from "next/server"; -import { withSession } from "supertokens-node/nextjs"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; -// @ts-ignore -import { backendConfig } from "@/app/config/backend"; - -SuperTokens.init(backendConfig()); - -export async function POST(request: NextRequest) { - return withSession(request, async (err, session) => { - if (err) { - return NextResponse.json(err, { status: 500 }); - } - // All validator checks have passed and the user has a verified email address - return NextResponse.json({ message: "Your email is verified!" }); - }, - { - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - } - ); -} -``` - - - - -```tsx -import { Controller, Post, UseGuards, Request, Response, Session } from "@nestjs/common"; -import { SessionContainer, SessionClaimValidator } from "supertokens-node/recipe/session"; -// @ts-ignore -import { AuthGuard } from './auth/auth.guard'; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -@Controller() -export class ExampleController { - @Post('example') - @UseGuards(new AuthGuard({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators: SessionClaimValidator[]) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - })) - async postExample(@Session() session: SessionContainer): Promise { - // All validator checks have passed and the user has a verified email address - return true; - } -} -``` - - - - - - - - -```go -import ( - "net/http" - - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - _ = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - session.VerifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }, exampleAPI).ServeHTTP(rw, r) - }) -} - -func exampleAPI(w http.ResponseWriter, r *http.Request) { - // TODO: session is verified and all validators have passed.. -} -``` - - - - -```go -import ( - "net/http" - - "github.com/gin-gonic/gin" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - router := gin.New() - - // Wrap the API handler in session.VerifySession - router.POST("/likecomment", verifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }), exampleAPI) -} - -// This is a function that wraps the supertokens verification function -// to work the gin -func verifySession(options *sessmodels.VerifySessionOptions) gin.HandlerFunc { - return func(c *gin.Context) { - session.VerifySession(options, func(rw http.ResponseWriter, r *http.Request) { - c.Request = c.Request.WithContext(r.Context()) - c.Next() - })(c.Writer, c.Request) - // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly - c.Abort() - } -} - -func exampleAPI(c *gin.Context) { - // TODO: session is verified and all claim validators pass. -} -``` - - - - -```go -import ( - "net/http" - - "github.com/go-chi/chi" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - r := chi.NewRouter() - - // Wrap the API handler in session.VerifySession - r.Post("/likecomment", session.VerifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }, exampleAPI)) -} - -func exampleAPI(w http.ResponseWriter, r *http.Request) { - // TODO: session is verified and all claim validators pass. -} -``` - - - - -```go -import ( - "net/http" - - "github.com/gorilla/mux" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - router := mux.NewRouter() - - // Wrap the API handler in session.VerifySession - router.HandleFunc("/likecomment", session.VerifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }, exampleAPI)).Methods(http.MethodPost) -} - -func exampleAPI(w http.ResponseWriter, r *http.Request) { - // TODO: session is verified and all claim validators pass. -} -``` - - - - - - - - - -```python -from supertokens_python.recipe.session.framework.fastapi import verify_session -from supertokens_python.recipe.emailverification import EmailVerificationClaim -from supertokens_python.recipe.session import SessionContainer -from fastapi import Depends - -@app.post('/like_comment') # type: ignore -async def like_comment(session: SessionContainer = Depends( - verify_session( - # highlight-start - # We add the EmailVerificationClaim's is_verified validator - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \ - [EmailVerificationClaim.validators.is_verified()] - # highlight-end - ) -)): - # All validator checks have passed and the user has a verified email address - pass -``` - - - - -```python -from supertokens_python.recipe.session.framework.flask import verify_session -from supertokens_python.recipe.emailverification import EmailVerificationClaim - -@app.route('/update-jwt', methods=['POST']) # type: ignore -@verify_session( - # highlight-start - # We add the EmailVerificationClaim's is_verified validator - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \ - [EmailVerificationClaim.validators.is_verified()] - # highlight-end -) -def like_comment(): - # All validator checks have passed and the user has a verified email address - pass -``` - - - - -```python -from supertokens_python.recipe.session.framework.django.asyncio import verify_session -from django.http import HttpRequest -from supertokens_python.recipe.emailverification import EmailVerificationClaim - -@verify_session( - # highlight-start - # We add the EmailVerificationClaim's is_verified validator - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \ - [EmailVerificationClaim.validators.is_verified()] - # highlight-end -) -async def like_comment(request: HttpRequest): - # All validator checks have passed and the user has a verified email address - pass -``` - - - - - - - -We add the SDK's `EmailVerificationClaim` validator to the `verifySession` middleware call as shown above, and that will only allow access if the email is verified, else it will return `403` to the frontend. - - - - - - - -import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" - -## Step 4: Protecting frontend routes - - - - - - - -```tsx -import Session from "supertokens-web-js/recipe/session"; -import { EmailVerificationClaim } from "supertokens-web-js/recipe/emailverification"; - -async function shouldLoadRoute(): Promise { - if (await Session.doesSessionExist()) { - // highlight-start - let validationErrors = await Session.validateClaims(); - - if (validationErrors.length === 0) { - // user has verified their email address - return true; - } else { - for (const err of validationErrors) { - if (err.id === EmailVerificationClaim.id) { - // email is not verified - } - } - } - // highlight-end - } - // a session does not exist, or email is not verified - return false -} -``` - - - - -```tsx -import supertokensSession from "supertokens-web-js-script/recipe/session"; -import supertokensEmailVerification from "supertokens-web-js-script/recipe/emailverification"; -async function shouldLoadRoute(): Promise { - if (await supertokensSession.doesSessionExist()) { - // highlight-start - let validationErrors = await supertokensSession.validateClaims(); - - if (validationErrors.length === 0) { - // user has verified their email address - return true; - } else { - for (const err of validationErrors) { - if (err.id === supertokensEmailVerification.EmailVerificationClaim.id) { - // email is not verified - } - } - } - // highlight-end - } - // a session does not exist, or email is not verified - return false -} -``` - - - - -In your protected routes, you need to first check if a session exists, and then call the `Session.validateClaims` function as shown above. This function inspects the session's contents and runs claim validators on them. If a claim validator fails, it will be reflected in the `validationErrors` variable. The `EmailVerificationClaim` validator will be automatically checked by this function since you have initialized the email verification recipe. - -
Handling 403 responses on the frontend - - - -If your frontend queries a protected API on your backend and it fails with a 403, you can call the `validateClaims` function and loop through the errors to know which claim has failed: - -```tsx -import axios from "axios"; -import Session from "supertokens-web-js/recipe/session"; -import { EmailVerificationClaim, sendVerificationEmail } from "supertokens-web-js/recipe/emailverification"; - -async function callProtectedRoute() { - try { - let response = await axios.get("^{form_apiDomain}/protectedroute"); - } catch (error) { - // highlight-start - if (axios.isAxiosError(error) && error.response?.status === 403) { - let validationErrors = await Session.validateClaims(); - for (let err of validationErrors) { - if (err.id === EmailVerificationClaim.id) { - // email verification claim check failed - // We call the sendEmail function defined in the next section to send the verification email. - // await sendEmail(); - } else { - // some other claim check failed (from the global validators list) - } - } - // highlight-end - - } - } -} -``` - - - -
- -
- - - - - - - -```tsx -import SuperTokens from 'supertokens-react-native'; - -async function checkIfEmailIsVerified() { - if (await SuperTokens.doesSessionExist()) { - - // highlight-start - let isVerified: boolean = (await SuperTokens.getAccessTokenPayloadSecurely())["st-ev"].v; - - if (isVerified) { - // TODO.. - } else { - // TODO.. - } - // highlight-end - } -} -``` - - - - - -```kotlin -import android.app.Application -import com.supertokens.session.SuperTokens -import org.json.JSONObject - -class MainApplication: Application() { - fun checkIfEmailIsVerified() { - val accessTokenPayload: JSONObject = SuperTokens.getAccessTokenPayloadSecurely(this); - val isVerified: Boolean = (accessTokenPayload.get("st-ev") as JSONObject).get("v") as Boolean - if (isVerified) { - // TODO.. - } else { - // TODO.. - } - } -} -``` - - - - - -```swift -import UIKit -import SuperTokensIOS - -fileprivate class ViewController: UIViewController { - func checkIfEmailIsVerified() { - if let accessTokenPayload: [String: Any] = try? SuperTokens.getAccessTokenPayloadSecurely(), let emailVerificationObject: [String: Any] = accessTokenPayload["st-ev"] as? [String: Any], let isVerified: Bool = emailVerificationObject["v"] as? Bool { - if isVerified { - // Email is verified - } else { - // Email is not verified - } - } - } -} -``` - - - - - -```dart -import 'package:supertokens_flutter/supertokens.dart'; - -Future checkIfEmailIsVerified() async { - var accessTokenPayload = await SuperTokens.getAccessTokenPayloadSecurely(); - - if (accessTokenPayload.containsKey("st-ev")) { - Map emailVerificationObject = accessTokenPayload["st-ev"]; - - if (emailVerificationObject.containsKey("v")) { - bool isVerified = emailVerificationObject["v"]; - - if (isVerified) { - // Email is verified - } else { - // Email is not verified - } - } - } -} -``` - - - - - -
Handling 403 responses on the frontend - -If your frontend queries a protected API on your backend and it fails with a 403, you can check the value of the `st-ev` claim in the access token payload. If it is set to false you can send the verification email - -
- -
-
- -import AppInfoForm from "/src/components/appInfoForm" - -## Step 5: Sending the email verification email - -When the email verification validators fail, or post sign up, you want to redirect the user to a screen telling them that a verification email has been sent to them. On this screen, you should call the following API - - - - - - - -```tsx -import { sendVerificationEmail } from "supertokens-web-js/recipe/emailverification"; - -async function sendEmail() { - try { - let response = await sendVerificationEmail(); - if (response.status === "EMAIL_ALREADY_VERIFIED_ERROR") { - // This can happen if the info about email verification in the session was outdated. - // Redirect the user to the home page - window.location.assign("/home"); - } else { - // email was sent successfully. - window.alert("Please check your email and click the link in it") - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - -```tsx -import supertokensEmailVerification from "supertokens-web-js/recipe/emailverification"; - -async function sendEmail() { - try { - let response = await supertokensEmailVerification.sendVerificationEmail(); - if (response.status === "EMAIL_ALREADY_VERIFIED_ERROR") { - // This can happen if the info about email verification in the session was outdated. - // Redirect the user to the home page - window.location.assign("/home"); - } else { - // email was sent successfully. - window.alert("Please check your email and click the link in it") - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - - - - - - - -You should create a new screen on your app that asks the user to enter their email to which an email will be sent. This screen should ideally be linked to from the sign in form. - -Once the user has enters their email, you can call the following API to send an email verification email to that user: - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/user/email/verify/token' \ ---header 'Authorization: Bearer ...' -``` - - - -The response body from the API call has a `status` property in it: -- `status: "OK"`: An email was sent to the user successfully. -- `status: "EMAIL_ALREADY_VERIFIED_ERROR"`: This status can be returned if the info about email verification in the session was outdated. Redirect the user to the home page. -- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. - - - -You do not need to add the tenant ID to the path here because the backend fetches the tenantId of the user from the session token. - - - - - - -:::note -The API for sending an email verification email requires an active session. If you are using our frontend SDKs, then the session tokens should automatically get attached to the request. -::: - -### Changing the email verification link domain / path -By default, the email verification link will point to the `websiteDomain` that is configured on the backend, on the `/auth/verify-email` route (where `/auth` is the default value of `websiteBasePath`). - -If you want to change this to a different path, a different domain, or deep link it to your mobile / desktop app, then you can do so on the backend in the following way: - - - - -```tsx -import SuperTokens from "supertokens-node"; -import EmailVerification from "supertokens-node/recipe/emailverification"; - -SuperTokens.init({ - supertokens: { - connectionURI: "...", - }, - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "..." - }, - recipeList: [ - EmailVerification.init({ - mode: "OPTIONAL", - // highlight-start - emailDelivery: { - override: (originalImplementation) => { - return { - ...originalImplementation, - sendEmail(input) { - return originalImplementation.sendEmail({ - ...input, - emailVerifyLink: input.emailVerifyLink.replace( - // This is: `${websiteDomain}${websiteBasePath}/verify-email` - "http://localhost:3000/auth/verify-email", - "http://localhost:3000/your/path" - ) - } - ) - }, - } - } - } - // highlight-end - }) - ] -}); -``` - - - -```go -import ( - "strings" - - "github.com/supertokens/supertokens-golang/ingredients/emaildelivery" - "github.com/supertokens/supertokens-golang/recipe/emailverification" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - supertokens.Init(supertokens.TypeInput{ - RecipeList: []supertokens.Recipe{ - emailverification.Init(evmodels.TypeInput{ - Mode: evmodels.ModeOptional, - // highlight-start - EmailDelivery: &emaildelivery.TypeInput{ - Override: func(originalImplementation emaildelivery.EmailDeliveryInterface) emaildelivery.EmailDeliveryInterface { - ogSendEmail := *originalImplementation.SendEmail - - (*originalImplementation.SendEmail) = func(input emaildelivery.EmailType, userContext supertokens.UserContext) error { - // This is: `${websiteDomain}${websiteBasePath}/verify-email` - input.EmailVerification.EmailVerifyLink = strings.Replace( - input.EmailVerification.EmailVerifyLink, - "http://localhost:3000/auth/verify-email", - "http://localhost:3000/your/path", 1, - ) - return ogSendEmail(input, userContext) - } - return originalImplementation - }, - }, - // highlight-end - }), - }, - }) -} -``` - - - -```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import emailverification -from supertokens_python.ingredients.emaildelivery.types import EmailDeliveryConfig -from supertokens_python.recipe.emailverification.types import EmailDeliveryOverrideInput, EmailTemplateVars -from typing import Dict, Any - - -def custom_email_delivery(original_implementation: EmailDeliveryOverrideInput) -> EmailDeliveryOverrideInput: - original_send_email = original_implementation.send_email - - # highlight-start - async def send_email(template_vars: EmailTemplateVars, user_context: Dict[str, Any]) -> None: - - # This is: `${websiteDomain}${websiteBasePath}/verify-email` - template_vars.email_verify_link = template_vars.email_verify_link.replace( - "http://localhost:3000/auth/verify-email", "http://localhost:3000/your/path") - - return await original_send_email(template_vars, user_context) - # highlight-end - - original_implementation.send_email = send_email - return original_implementation - - -init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore - recipe_list=[ - emailverification.init( - mode="OPTIONAL", - # highlight-next-line - email_delivery=EmailDeliveryConfig(override=custom_email_delivery)) - ] -) -``` - - - - - - - -For a multi tenant setup, the input to the `sendEmail` function will also contain the `tenantId`. You can use this to determine the correct value to set for the websiteDomain in the generated link. - - - -## Step 6: Verifying the email post link clicked - -Once the user clicks the email verification link, and it opens your app, you can call the following function which will automatically extract the token and tenantId (if using a multi tenant setup) from the link and call the token verification API. - - - - - - - -```tsx -import { verifyEmail } from "supertokens-web-js/recipe/emailverification"; - -async function consumeVerificationCode() { - try { - let response = await verifyEmail(); - if (response.status === "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR") { - // This can happen if the verification code is expired or invalid. - // You should ask the user to retry - window.alert("Oops! Seems like the verification link expired. Please try again") - window.location.assign("/auth/verify-email") // back to the email sending screen. - } else { - // email was verified successfully. - window.location.assign("/home") - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - -```tsx -import supertokensEmailVerification from "supertokens-web-js/recipe/emailverification"; - -async function consumeVerificationCode() { - try { - let response = await supertokensEmailVerification.verifyEmail(); - if (response.status === "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR") { - // This can happen if the verification code is expired or invalid. - // You should ask the user to retry - window.alert("Oops! Seems like the verification link expired. Please try again") - window.location.assign("/auth/verify-email") // back to the email sending screen. - } else { - // email was verified successfully. - window.location.assign("/home") - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - - - - - - - -Once the user clicks the email verification link, and it opens as a deep link into your mobile app, you can extract the token from the link and call the verification API as shown below: - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/user/email/verify' \ ---header 'Content-Type: application/json; charset=utf-8' \ ---data-raw '{ - "method": "token", - "token": "ZTRiOTBjNz...jI5MTZlODkxw" -}' -``` - - - - -For a multi tenancy setup, the `` value can be fetched from `tenantId` query parameter from the email verification link. If it's not there in the link, you can use the value `"public"` (which is the default tenant). - - - - - - -The response body from the API call has a `status` property in it: -- `status: "OK"`: Email verification was successful. -- `status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR"`: This can happen if the verification code is expired or invalid. You should ask the user to retry. -- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. - - - - -:::caution -- This API doesn't require an active session to succeed. -- If you are calling the above API on page load, there is an edge case in which email clients might open the verification link in the email (for scanning purposes) and consume the token in the URL. This would lead to issues in which an attacker could sign up using someone else's email and end up with a veriifed status! - - To prevent this, on page load, you should check if a session exists, and if it does, only then call the above API. If a session does not exist, you should first show a button, which when clicked would call the above API (email clients won't automatically click on this button). The button text could be something like "Click here to verify your email". -::: - - - - - - - -## See also - -- [Post email verification action](../common-customizations/email-verification/handling-email-verification-success) -- [Change email verification link's lifetime](../common-customizations/email-verification/changing-token-lifetime) -- [Customise email template or email delivery method](../email-delivery/about) -- [Manually changing email verification status for a user](../common-customizations/email-verification/changing-email-verification-status) -- [Generating email verification links manually](../common-customizations/email-verification/generate-link-manually) -- 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) in `REQUIRED` mode. - - \ No newline at end of file diff --git a/v2/emailpassword/custom-ui/handling-session-tokens.mdx b/v2/emailpassword/custom-ui/handling-session-tokens.mdx index bdfa1a21f..6ff934e75 100644 --- a/v2/emailpassword/custom-ui/handling-session-tokens.mdx +++ b/v2/emailpassword/custom-ui/handling-session-tokens.mdx @@ -4,412 +4,7 @@ title: Handling session tokens hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -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"; -import AppInfoForm from "/src/components/appInfoForm" -import {Question, Answer}from "/src/components/question" + -# Handling session tokens - -There are two modes ways in which you can use sessions with SuperTokens: -- Using `httpOnly` cookies -- Authorization bearer token. - -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 - -### For Web - - - - - -:::success -No action required. -::: - -Our frontend SDK handles everything for you. You only need to make sure that you have called `supertokens.init` before making any network requests. - -Our SDK adds interceptors to `fetch` and `XHR` (used by `axios`) to save and add session tokens from and to the request. - -By default, our web SDKs use cookies to provide credentials. - - - - - -import NetworkInterceptors from "/session/reusableMD/networkInterceptors.mdx" - -### For React-Native -Our frontend SDK handles everything for you. You only need to make sure that you have added our network interceptors as shown below - - - -:::note -By default our mobile SDKs use a bearer token in the Authorization header to provide credentials. -::: - -### For Android - - - - -```kotlin -import android.app.Application -import com.supertokens.session.SuperTokens -import com.supertokens.session.SuperTokensHttpURLConnection -import com.supertokens.session.SuperTokensPersistentCookieStore -import java.net.URL -import java.net.HttpURLConnection - -class MainApplication: Application() { - override fun onCreate() { - super.onCreate() - // TODO: Make sure to call SuperTokens.init - } - - fun makeRequest() { - val url = URL("") - val connection = SuperTokensHttpURLConnection.newRequest(url, object: SuperTokensHttpURLConnection.PreConnectCallback { - override fun doAction(con: HttpURLConnection?) { - // TODO: Use `con` to set request method, headers etc - } - }) - - // Handle response using connection object, for example: - if (connection.responseCode == 200) { - // TODO: implement - } - } -} -``` - -:::note -When making network requests you do not need to call `HttpURLConnection.connect` because SuperTokens does this for you. -::: - - - - -```kotlin -import android.content.Context -import com.supertokens.session.SuperTokens -import com.supertokens.session.SuperTokensInterceptor -import okhttp3.OkHttpClient -import retrofit2.Retrofit - -class NetworkManager { - fun getClient(context: Context): OkHttpClient { - val clientBuilder = OkHttpClient.Builder() - clientBuilder.addInterceptor(SuperTokensInterceptor()) - // TODO: Make sure to call SuperTokens.init - - val client = clientBuilder.build() - - // REQUIRED FOR RETROFIT ONLY - val instance = Retrofit.Builder() - .baseUrl("") - .client(client) - .build() - - return client - } - - fun makeRequest(context: Context) { - val client = getClient(context) - // Use client to make requests normally - } -} -``` - - - - -:::note -By default our mobile SDKs use a bearer token in the Authorization header to provide credentials. -::: - -### For iOS - - - - - -

Using URLSession.shared

- -```swift -import Foundation -import SuperTokensIOS - -fileprivate class NetworkManager { - func setupSuperTokensInterceptor() { - URLProtocol.registerClass(SuperTokensURLProtocol.self) - } -} -``` - -

Using a custom URLSession instance

- -```swift -import Foundation -import SuperTokensIOS - -fileprivate class NetworkManager { - func setupSuperTokensInterceptor() { - let configuration = URLSessionConfiguration.default - configuration.protocolClasses = [SuperTokensURLProtocol.self] - let session = URLSession(configuration: configuration) - - // Use session when making network requests - } -} -``` - -
- - - -```swift -import Foundation -import SuperTokensIOS -import Alamofire - -fileprivate class NetworkManager { - func setupSuperTokensInterceptor() { - let configuration = URLSessionConfiguration.af.default - configuration.protocolClasses = [SuperTokensURLProtocol.self] + (configuration.protocolClasses ?? []) - let session = Session(configuration: configuration) - - // Use session when making network requests - } -} -``` - - - -
- -:::note -By default our mobile SDKs use a bearer token in the Authorization header to provide credentials. -::: - -### For Flutter - - - - -You can make requests as you normally would with `http`, the only difference is that you import the client from the supertokens package instead. - -```dart -// Import http from the SuperTokens package -import 'package:supertokens_flutter/http.dart' as http; - -Future makeRequest() async { - Uri uri = Uri.parse("http://localhost:3001/api"); - var response = await http.get(uri); - // handle response -} -``` - -

Using a custom http client

- -If you use a custom http client and want to use SuperTokens, you can simply provide the SDK with your client. All requests will continue to use your client along with the session logic that SuperTokens provides. - -```dart -// Import http from the SuperTokens package -import 'package:supertokens_flutter/http.dart' as http; - -Future makeRequest() async { - Uri uri = Uri.parse("http://localhost:3001/api"); - - // Initialise your custom client - var customClient = http.Client(); - // provide your custom client to SuperTokens - var httpClient = http.Client(client: customClient); - - var response = await httpClient.get(uri); - // handle response -} -``` - -
- - -

Add the SuperTokens interceptor

- -Use the extension method provided by the SuperTokens SDK to enable interception on your Dio client. This allows the SuperTokens SDK to handle session tokens for you. - -```dart -import 'package:supertokens_flutter/dio.dart'; -import 'package:dio/dio.dart'; - -void setup() { - Dio dio = Dio(); // Create a Dio instance. - dio.addSupertokensInterceptor(); -} -``` - -

Making network requests

- -You can make requests as you normally would with `dio`. - -```dart -import 'package:supertokens_flutter/dio.dart'; -import 'package:dio/dio.dart'; - -void setup() { - Dio dio = Dio( - // Provide your config here - ); - dio.addSupertokensInterceptor(); - - var response = dio.get("http://localhost:3001/api"); - // handle response -} -``` - -
-
- -:::note -By default our mobile SDKs use a bearer token in the Authorization header to provide credentials. -::: - -## If not using our frontend SDK - -:::caution -We highly recommend using our frontend SDK to handle session token management. It will save you a lot of time. -::: - -In this case, you will need to manually handle the tokens and session refreshing, and decide if you are going to use header or cookie-based sessions. - -For browsers, we recommend cookies, while for mobile apps (or if you don't want to use the built-in cookie manager) you should use header-based sessions. - - - - - -### On login - -You should attach the `st-auth-mode` header to calls to the login API, but this header is safe to attach to all requests. In this case it should be set to "cookie". - -The login API will return the following headers: -- `Set-Cookie`: This will contain the `sAccessToken`, `sRefreshToken` cookies which will be `httpOnly` and will be automatically mananaged by the browser. For mobile apps, you will need to setup cookie handling yourself, use our SDK or use a header based authentication mode. -- `front-token` header: This contains information about the access token: - - The userID - - The expiry time of the access token - - The payload added by you in the access token. - - Here is the structure of the token: - ```tsx - let frontTokenFromRequestHeader = "..."; - let frontTokenDecoded = JSON.parse(decodeURIComponent(escape(atob(frontTokenFromRequestHeader)))); - console.log(frontTokenDecoded); - /* - { - ate: 1665226412455, // time in milliseconds for when the access token will expire, and then a refresh is required - uid: "....", // user ID - up: { - sub: "..", - iat: .., - ... // other access token payload - } - } - - */ - ``` - - This token is mainly used for cookie based auth because you don't have access to the actual access token on the frontend (for security reasons), but may want to read its payload (for example to know the user's role). This token itself is not signed and hence can't be used in place of the access token itself. You may want to save this token in localstorage or in frontend cookies (using `document.cookies`). - -- `anti-csrf` header (optional): By default it's not required, so it's not sent. But if this is sent, you should save this token as well for use when making requests. - -### Making network requests to protected APIs - -The `sAccessToken` will get attached to the request automatically by the browser. Other than that, you need to add the following headers to the request: -- `rid: "anti-csrf"` - this prevents against anti-CSRF requests. If your `apiDomain` and `websiteDomain` values are exactly the same, then this is not necessary. -- `anti-csrf` header (optional): If this was provided to you during login, then you need to add that token as the value of this header. -- You need to set the `credentials` header to `true` or `include`. This is achieved different based on the library you are using to make requests. - -An API call can potentially update the `sAccessToken` and `front-token` tokens, for example if you call the `mergeIntoAccessTokenPayload` function on the `session` object on the backend. This kind of update is reflected in the response headers for your API calls. The headers will contain new values for: -- `sAccessToken`: This will be as a new `Set-Cookie` header and will be managed by the browser automatically. -- `front-token`: This should be read and saved by you in the same way as it's being done during login. - -### Handling session refreshing -If any of your API calls return with a status code of `401`, it means that the access token has expired. This will require you to refresh the session before retrying the same API call. - -You can call the refresh API as follows: - - - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/session/refresh' \ ---header 'Cookie: sRefreshToken=...' -``` - -:::note -- You may also need to add the `anti-csrf` header to the request if that was provided to you during sign in. -- The cURL command above shows the `sRefreshToken` cookie as well, but this is added by the web browser automatically, so you don't need to add it explicitly. -::: - - - -The result of a session refresh will be either: -- Status code `200`: This implies a succesful refresh. The set of tokens returned here will be the same as when the user logs in, so you can handle them in the same way. -- Status code `401`: This means that the refresh token is invalid, or has been revoked. You must ask the user to relogin. Remember to clear the `front-token` that you saved on the frontend earlier. - - - - - -### On login - -You should attach the `st-auth-mode` header to calls to the login API, but this header is safe to attach to all requests. In this case it should be set to "header". - -The login API will return the following headers: -- `st-access-token`: This contains the current access token associated with the session. You should save this in your application (e.g., in frontend localstorage). -- `st-refresh-token`: This contains the current refresh token associated with the session. You should save this in your application (e.g., in frontend localstorage). - -### Making network requests to protected APIs - -You need to add the following headers to request: -- `authorization: Bearer {access-token}` -- You need to set the `credentials` to `true` or `include`. This is achieved different based on the library you are using to make requests. - -An API call can potentially update the `access-token`, for example if you call the `mergeIntoAccessTokenPayload` function on the `session` object on the backend. This kind of update is reflected in the response headers for your API calls. The headers will contain new values for `st-access-token` - -These should be read and saved by you in the same way as it's being done during login. - -### Handling session refreshing -If any of your API calls return with a status code of `401`, it means that the access token has expired. This will require you to refresh the session before retrying the same API call. - -You can call the refresh API as follows: - - - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/session/refresh' \ ---header 'authorization: Bearer {refresh-token}' -``` - - - -The result of a session refresh will be either: -- Status code `200`: This implies a succesful refresh. The set of tokens returned here will be the same as when the user logs in, so you can handle them in the same way. -- Status code `401`: This means that the refresh token is invalid, or has been revoked. You must ask the user to relogin. Remember to clear the `st-refresh-token` and `st-access-token` that you saved on the frontend earlier. - - - - diff --git a/v2/emailpassword/custom-ui/init/backend.mdx b/v2/emailpassword/custom-ui/init/backend.mdx index 99b5f5bc6..577fc5a32 100644 --- a/v2/emailpassword/custom-ui/init/backend.mdx +++ b/v2/emailpassword/custom-ui/init/backend.mdx @@ -4,912 +4,8 @@ title: "Step 2: Backend" hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import TabItem from '@theme/TabItem'; -import NodeJSFrameworkSubTabsServerless from "/src/components/tabs/NodeJSFrameworkSubTabsServerless" -import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs" -import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" -import AppInfoForm from "/src/components/appInfoForm" -import CoreInjector from "/src/components/coreInjector" -import {Question, Answer}from "/src/components/question" -import BackendSDKCasing from "/src/components/BackendSDKCasing" -# Backend Integration + -## Supported frameworks - -import TechStackSupport from "../../../community/reusableMD/supported-tech-stacks-backend.mdx" - - - -## 1) Install - - - - -```bash -npm i -s supertokens-node -``` - - - - -```bash -go get github.com/supertokens/supertokens-golang -``` - - - - -```bash -pip install supertokens-python -``` - - - - -## 2) Initialise SuperTokens - - - - - -Add the code below to your server's init file. - - - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import EmailPassword from "supertokens-node/recipe/emailpassword"; - -supertokens.init({ - framework: "express", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}", - }, - recipeList: [ - EmailPassword.init(), // initializes signin / sign up features - Session.init() // initializes session features - ] -}); -``` - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import EmailPassword from "supertokens-node/recipe/emailpassword"; - -supertokens.init({ - framework: "hapi", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}", - }, - recipeList: [ - EmailPassword.init(), // initializes signin / sign up features - Session.init() // initializes session features - ] -}); -``` - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import EmailPassword from "supertokens-node/recipe/emailpassword"; - -supertokens.init({ - framework: "fastify", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}", - }, - recipeList: [ - EmailPassword.init(), // initializes signin / sign up features - Session.init() // initializes session features - ] -}); -``` - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import EmailPassword from "supertokens-node/recipe/emailpassword"; - -supertokens.init({ - framework: "koa", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}", - }, - recipeList: [ - EmailPassword.init(), // initializes signin / sign up features - Session.init() // initializes session features - ] -}); -``` - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import EmailPassword from "supertokens-node/recipe/emailpassword"; - -supertokens.init({ - framework: "loopback", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}", - }, - recipeList: [ - EmailPassword.init(), // initializes signin / sign up features - Session.init() // initializes session features - ] -}); -``` - - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/emailpassword" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - apiBasePath := "^{form_apiBasePath}" - websiteBasePath := "^{form_websiteBasePath}" - err := supertokens.Init(supertokens.TypeInput{ - Supertokens: &supertokens.ConnectionInfo{ - ^{coreInjector_connection_uri_comment} - ConnectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}APIKey: ^{coreInjector_api_key}, - }, - AppInfo: supertokens.AppInfo{ - AppName: "^{form_appName}", - APIDomain: "^{form_apiDomain}", - WebsiteDomain: "^{form_websiteDomain}", - APIBasePath: &apiBasePath, - WebsiteBasePath: &websiteBasePath, - }, - RecipeList: []supertokens.Recipe{ - emailpassword.Init(nil), - session.Init(nil), - }, - }) - - if err != nil { - panic(err.Error()) - } -} -``` - - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig -from supertokens_python.recipe import emailpassword, session - -init( - app_info=InputAppInfo( - app_name="^{form_appName}", - api_domain="^{form_apiDomain}", - website_domain="^{form_websiteDomain}", - api_base_path="^{form_apiBasePath}", - website_base_path="^{form_websiteBasePath}" - ), - supertokens_config=SupertokensConfig( - ^{coreInjector_connection_uri_comment_with_hash} - connection_uri=^{coreInjector_uri} - ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} - ), - framework='fastapi', - recipe_list=[ - session.init(), # initializes session features - emailpassword.init() - ], - mode='asgi' # use wsgi if you are running using gunicorn -) -``` - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig -from supertokens_python.recipe import emailpassword, session - -init( - app_info=InputAppInfo( - app_name="^{form_appName}", - api_domain="^{form_apiDomain}", - website_domain="^{form_websiteDomain}", - api_base_path="^{form_apiBasePath}", - website_base_path="^{form_websiteBasePath}" - ), - supertokens_config=SupertokensConfig( - ^{coreInjector_connection_uri_comment_with_hash} - connection_uri=^{coreInjector_uri} - ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} - ), - framework='flask', - recipe_list=[ - session.init(), # initializes session features - emailpassword.init() - ] -) -``` - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig -from supertokens_python.recipe import emailpassword, session - -init( - app_info=InputAppInfo( - app_name="^{form_appName}", - api_domain="^{form_apiDomain}", - website_domain="^{form_websiteDomain}", - api_base_path="^{form_apiBasePath}", - website_base_path="^{form_websiteBasePath}" - ), - supertokens_config=SupertokensConfig( - ^{coreInjector_connection_uri_comment_with_hash} - connection_uri=^{coreInjector_uri} - ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} - ), - framework='django', - recipe_list=[ - session.init(), # initializes session features - emailpassword.init() - ], - mode='asgi' # use wsgi if you are running django server in sync mode -) -``` - - - - - - - - - - -## 3) Add the SuperTokens APIs & CORS setup - - - - - - - - -:::important -- Add the `middleware` BEFORE all your routes. -- Add the `cors` middleware BEFORE the SuperTokens middleware as shown below. -::: - -```tsx -import express from "express"; -import cors from "cors"; -import supertokens from "supertokens-node"; -import { middleware } from "supertokens-node/framework/express"; - -let app = express(); - -app.use(cors({ - // highlight-start - origin: "^{form_websiteDomain}", - allowedHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], - credentials: true, - // highlight-end -})); - -// IMPORTANT: CORS should be before the below line. -// highlight-next-line -app.use(middleware()); - -// ...your API routes -``` -This `middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): - -- `POST ^{form_apiBasePath}/signup`: For signing up a user with email & password -- `POST ^{form_apiBasePath}/signin`: For signing in a user with email & password - - - - -Register the `plugin`. - -```tsx -import Hapi from "@hapi/hapi"; -import supertokens from "supertokens-node"; -import { plugin } from "supertokens-node/framework/hapi"; - -let server = Hapi.server({ - port: 8000, - routes: { - // highlight-start - cors: { - origin: ["^{form_websiteDomain}"], - additionalHeaders: [...supertokens.getAllCORSHeaders()], - credentials: true, - } - // highlight-end - } -}); - -(async () => { - // highlight-next-line - await server.register(plugin); - - await server.start(); -})(); - -// ...your API routes -``` -This `plugin` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)) as well take care of all the errors thrown by the Supertokens library: - -- `POST ^{form_apiBasePath}/signup`: For signing up a user with email & password -- `POST ^{form_apiBasePath}/signin`: For signing in a user with email & password - - - - -Register the `plugin`. Also register [`@fastify/formbody`](https://github.com/fastify/fastify-formbody) plugin. - -```tsx -import cors from "@fastify/cors"; -import supertokens from "supertokens-node"; -import { plugin } from "supertokens-node/framework/fastify"; -import formDataPlugin from "@fastify/formbody"; - -import fastifyImport from "fastify"; - -let fastify = fastifyImport(); - -// ...other middlewares -// highlight-start -fastify.register(cors, { - origin: "^{form_websiteDomain}", - allowedHeaders: ['Content-Type', ...supertokens.getAllCORSHeaders()], - credentials: true, -}); -// highlight-end - -(async () => { - // highlight-next-line - await fastify.register(formDataPlugin); - // highlight-next-line - await fastify.register(plugin); - - await fastify.listen(8000); -})(); - -// ...your API routes -``` -This `plugin` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): - -- `POST ^{form_apiBasePath}/signup`: For signing up a user with email & password -- `POST ^{form_apiBasePath}/signin`: For signing in a user with email & password - - - - -:::important -Add the `middleware` BEFORE all your routes. -::: - -```tsx -import Koa from "koa"; -import cors from '@koa/cors'; -import supertokens from "supertokens-node"; -import { middleware } from "supertokens-node/framework/koa"; - -let app = new Koa(); - -app.use(cors({ - // highlight-start - origin: "^{form_websiteDomain}", - allowHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], - credentials: true, - // highlight-end -})); - -// highlight-next-line -app.use(middleware()); - -// ...your API routes -``` -This `middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): - -- `POST ^{form_apiBasePath}/signup`: For signing up a user with email & password -- `POST ^{form_apiBasePath}/signin`: For signing in a user with email & password - - - - -:::important -Add the `middleware` BEFORE all your routes. -::: - -```tsx -import { RestApplication } from "@loopback/rest"; -import supertokens from "supertokens-node"; -import { middleware } from "supertokens-node/framework/loopback"; - -let app = new RestApplication({ - rest: { - cors: { - // highlight-start - origin: "^{form_websiteDomain}", - allowedHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], - credentials: true - // highlight-end - } - } -}); - -// highlight-next-line -app.middleware(middleware); - -// ...your API routes -``` -This `middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): - -- `POST ^{form_apiBasePath}/signup`: For signing up a user with email & password -- `POST ^{form_apiBasePath}/signin`: For signing in a user with email & password - - - - - - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "net/http" - "strings" - - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - //highlight-start - http.ListenAndServe("SERVER ADDRESS", corsMiddleware( - supertokens.Middleware(http.HandlerFunc(func(rw http.ResponseWriter, - //highlight-end - r *http.Request) { - // TODO: Handle your APIs.. - - })))) -} - -func corsMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(response http.ResponseWriter, r *http.Request) { - response.Header().Set("Access-Control-Allow-Origin", "^{form_websiteDomain}") - response.Header().Set("Access-Control-Allow-Credentials", "true") - if r.Method == "OPTIONS" { - // we add content-type + other headers used by SuperTokens - response.Header().Set("Access-Control-Allow-Headers", - strings.Join(append([]string{"Content-Type"}, - //highlight-start - supertokens.GetAllCORSHeaders()...), ",")) - //highlight-end - response.Header().Set("Access-Control-Allow-Methods", "*") - response.Write([]byte("")) - } else { - next.ServeHTTP(response, r) - } - }) -} -``` - - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "net/http" - - "github.com/gin-contrib/cors" - "github.com/gin-gonic/gin" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - router := gin.New() - - // CORS - router.Use(cors.New(cors.Config{ - AllowOrigins: []string{"^{form_websiteDomain}"}, - AllowMethods: []string{"GET", "POST", "DELETE", "PUT", "OPTIONS"}, - AllowHeaders: append([]string{"content-type"}, - // highlight-next-line - supertokens.GetAllCORSHeaders()...), - AllowCredentials: true, - })) - - // Adding the SuperTokens middleware - // highlight-start - router.Use(func(c *gin.Context) { - supertokens.Middleware(http.HandlerFunc( - func(rw http.ResponseWriter, r *http.Request) { - c.Next() - })).ServeHTTP(c.Writer, c.Request) - // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly - c.Abort() - }) - // highlight-end - - // Add APIs and start server -} -``` - - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "github.com/go-chi/chi" - "github.com/go-chi/cors" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - r := chi.NewRouter() - - // CORS - r.Use(cors.Handler(cors.Options{ - AllowedOrigins: []string{"^{form_websiteDomain}"}, - AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, - AllowedHeaders: append([]string{"Content-Type"}, - //highlight-next-line - supertokens.GetAllCORSHeaders()...), - AllowCredentials: true, - })) - - // SuperTokens Middleware - //highlight-next-line - r.Use(supertokens.Middleware) - - // Add APIs and start server -} -``` - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "net/http" - - "github.com/gorilla/handlers" - "github.com/gorilla/mux" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - // TODO: Add APIs - - router := mux.NewRouter() - - // Adding handlers.CORS(options)(supertokens.Middleware(router))) - //highlight-start - http.ListenAndServe("SERVER ADDRESS", handlers.CORS( - handlers.AllowedHeaders(append([]string{"Content-Type"}, - supertokens.GetAllCORSHeaders()...)), - handlers.AllowedMethods([]string{"GET", "POST", "PUT", "HEAD", "OPTIONS"}), - handlers.AllowedOrigins([]string{"^{form_websiteDomain}"}), - handlers.AllowCredentials(), - )(supertokens.Middleware(router))) - //highlight-end -} -``` - - - -This `Middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): - -- `POST ^{form_apiBasePath}/signup`: For signing up a user with email & password -- `POST ^{form_apiBasePath}/signin`: For signing in a user with email & password - - - - - - -Use the `Middleware` (**BEFORE all your routes**) and the `get_all_cors_headers()` functions as shown below. - -```python -from supertokens_python import get_all_cors_headers -from fastapi import FastAPI -from starlette.middleware.cors import CORSMiddleware -from supertokens_python.framework.fastapi import get_middleware - -app = FastAPI() -# highlight-next-line -app.add_middleware(get_middleware()) - -# TODO: Add APIs - -app.add_middleware( - CORSMiddleware, - allow_origins=[ - "^{form_websiteDomain}" - ], - allow_credentials=True, - allow_methods=["GET", "PUT", "POST", "DELETE", "OPTIONS", "PATCH"], - # highlight-next-line - allow_headers=["Content-Type"] + get_all_cors_headers(), -) - -# TODO: start server -``` - - - - -- Use the `Middleware` (**BEFORE all your routes and after calling init function**) and the `get_all_cors_headers()` functions as shown below. -- Add a route to catch all paths and return a 404. This is needed because if we don't add this, then OPTIONS request for the APIs exposed by the `Middleware` will return a `404`. - -```python -from supertokens_python import get_all_cors_headers -from flask import Flask, abort -from flask_cors import CORS # type: ignore -from supertokens_python.framework.flask import Middleware - -app = Flask(__name__) -# highlight-next-line -Middleware(app) - -# TODO: Add APIs - -CORS( - app=app, - origins=[ - "^{form_websiteDomain}" - ], - supports_credentials=True, - # highlight-next-line - allow_headers=["Content-Type"] + get_all_cors_headers(), -) - -# This is required since if this is not there, then OPTIONS requests for -# the APIs exposed by the supertokens' Middleware will return a 404 -# highlight-start -@app.route('/', defaults={'u_path': ''}) # type: ignore -@app.route('/') # type: ignore -def catch_all(u_path: str): - abort(404) -# highlight-end - -# TODO: start server -``` - - - - -Use the `Middleware` and the `get_all_cors_headers()` functions as shown below in your `settings.py`. - -```python -from supertokens_python import get_all_cors_headers -from typing import List -from corsheaders.defaults import default_headers - -CORS_ORIGIN_WHITELIST = [ - "^{form_websiteDomain}" -] - -CORS_ALLOW_CREDENTIALS = True - -CORS_ALLOWED_ORIGINS = [ - "^{form_websiteDomain}" -] - -CORS_ALLOW_HEADERS: List[str] = list(default_headers) + [ - "Content-Type" - # highlight-next-line -] + get_all_cors_headers() - -INSTALLED_APPS = [ - 'corsheaders', - 'supertokens_python' -] - -MIDDLEWARE = [ - 'corsheaders.middleware.CorsMiddleware', - ..., - # highlight-next-line - 'supertokens_python.framework.django.django_middleware.middleware', -] -# TODO: start server -``` - - - - -This `Middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): - -- `POST ^{form_apiBasePath}/signup`: For signing up a user with email & password -- `POST ^{form_apiBasePath}/signin`: For signing in a user with email & password - - - - - - - -## 4) Add the SuperTokens error handler - - - - - - -```tsx -import express, { Request, Response, NextFunction } from 'express'; -import { errorHandler } from "supertokens-node/framework/express"; - -let app = express(); - -// ...your API routes - -// highlight-start -// Add this AFTER all your routes -app.use(errorHandler()) -// highlight-end - -// your own error handler -app.use((err: unknown, req: Request, res: Response, next: NextFunction) => { /* ... */ }); -``` - - - -No additional `errorHandler` is required. - - - - -Add the `errorHandler` **Before all your routes and plugin registration** - -```tsx -import Fastify from "fastify"; -import { errorHandler } from "supertokens-node/framework/fastify"; - -let fastify = Fastify(); - -// highlight-next-line -fastify.setErrorHandler(errorHandler()); - -// ...your API routes -``` - - - -No additional `errorHandler` is required. - - - - -No additional `errorHandler` is required. - - - - - - -:::info -You can skip this step -::: - - - - -:::info -You can skip this step -::: - - - - -## 5) Setup the SuperTokens core - -You need to now setup an instance of the SuperTokens core for your app (that your backend should connect to). You have two options: -- [Managed service](./core/saas-setup) -- Self hosted with your own database ([With Docker](./core/with-docker) or [Without Docker](./core/without-docker)) diff --git a/v2/emailpassword/custom-ui/init/core/managed-service.mdx b/v2/emailpassword/custom-ui/init/core/managed-service.mdx index 5dade6aca..726e4dc4a 100644 --- a/v2/emailpassword/custom-ui/init/core/managed-service.mdx +++ b/v2/emailpassword/custom-ui/init/core/managed-service.mdx @@ -4,95 +4,9 @@ title: Managed Service hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import TabItem from '@theme/TabItem'; -# Managed Service + -## Creating a development environment ✨ -- First, please [sign up](https://supertokens.com/auth) -- Select the auth method you want to use and follow the guided steps to integrate with our frontend and backend SDK if you have not done this already -Integration with SuperTokens SDKs -- Select a region and click the deploy button: -:::tip -You should select a region that is closest to your backend. -::: - -Deploying SuperTokens Core - -- After the deployment is complete the dashboard will look similar to this: -Deployed SuperTokens Core - -## Connecting the backend SDK with SuperTokens 🔌 -- Add the `connectionURI` and the `apiKey` shown on the dashboard to your code on the backend. - -SuperTokens managed service dashboard connectionURI and API key - - - - -```tsx -import supertokens from "supertokens-node"; - -supertokens.init({ - // highlight-start - supertokens: { - connectionURI: "", - apiKey: "" - }, - // highlight-end - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "..." - }, - recipeList: [] -}); -``` - - - - -```go -import "github.com/supertokens/supertokens-golang/supertokens" - -func main() { - supertokens.Init(supertokens.TypeInput{ - // highlight-start - Supertokens: &supertokens.ConnectionInfo{ - ConnectionURI: "", - APIKey: "", - }, - // highlight-end - }) -} - -``` - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig - -init( - app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), - # highlight-start - supertokens_config=SupertokensConfig( - connection_uri='', - api_key='' - ), - # highlight-end - framework='...', # type: ignore - recipe_list=[ - #... - ] -) -``` - - - diff --git a/v2/emailpassword/custom-ui/init/core/self-hosted-with-docker.mdx b/v2/emailpassword/custom-ui/init/core/self-hosted-with-docker.mdx index 1df643ac0..e31d7f6e6 100644 --- a/v2/emailpassword/custom-ui/init/core/self-hosted-with-docker.mdx +++ b/v2/emailpassword/custom-ui/init/core/self-hosted-with-docker.mdx @@ -4,268 +4,8 @@ title: With Docker hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import DatabaseTabs from "/src/components/tabs/DatabaseTabs" -import TabItem from '@theme/TabItem'; -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import DockerVersionProvider from "/src/components/dockerVersionProvider"; -# With Docker + -## Running the docker image 🚀 - - - - - - -```bash -docker run -p 3567:3567 -d registry.supertokens.io/supertokens/supertokens-mysql^{docker_version_mysql} -``` - -- To see all the env variables available, please see [the README file](https://github.com/supertokens/supertokens-docker-mysql/blob/master/README.md). -- The above command will start the container with an in-memory database. This means you **do not need to connect it to MySQL to test out SuperTokens**. -- When you are ready to connect it to your database, please visit the [Database setup section](../database-setup/mysql) - - - - - -```bash -docker run -p 3567:3567 -d registry.supertokens.io/supertokens/supertokens-postgresql^{docker_version_postgresql} -``` - -- To see all the env variables available, please see [the README file](https://github.com/supertokens/supertokens-docker-postgresql/blob/master/README.md). -- The above command will start the container with an in-memory database. This means you **do not need to connect it to PostgreSQL to test out SuperTokens**. -- When you are ready to connect it to your database, please visit the [Database setup section](../database-setup/postgresql) - - - - - -```bash -docker run -p 3567:3567 -d registry.supertokens.io/supertokens/supertokens-mongodb^{docker_version_mongodb} -``` - -- To see all the env variables available, please see [the README file](https://github.com/supertokens/supertokens-docker-mongodb/blob/master/README.md). -- The above command will start the container with an in-memory database. This means you **do not need to connect it to MongoDB to test out SuperTokens**. -- When you are ready to connect it to your database, please visit the [Database setup section](../database-setup/mongodb) - -:::caution -We do not offer login functionality with MongDB yet. We only offer session management. -::: - - - - - -## Testing that the service is running 🤞 -Open a browser and visit `http://localhost:3567/hello`. If you see a page that says `Hello` back, then the container was started successfully! - -If you are having issues with starting the docker image, please feel free to reach out to us [over email](mailto:team@supertokens.com) or [via Discord](https://supertokens.com/discord). - -:::tip -The `/hello` route checks whether the database connection is set up correctly and will only return a 200 status code if there is no issue. - -If you are using kubernetes or docker swarm, this endpoint is perfect for doing readiness and liveness probes. -::: - -## Connecting the backend SDK with SuperTokens 🔌 -- The default `port` for SuperTokens is `3567`. You can change this by binding a different port in the `docker run` command. For example, `docker run -p 8080:3567` will run SuperTokens on port `8080` on your machine. -- The connection info will go in the `supertokens` object in the `init` function on your backend: - - - - -```tsx -import supertokens from "supertokens-node"; - -supertokens.init({ - // highlight-start - supertokens: { - connectionURI: "http://localhost:3567", - apiKey: "someKey" // OR can be undefined - }, - // highlight-end - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "..." - }, - recipeList: [] -}); -``` - - - - -```go -import "github.com/supertokens/supertokens-golang/supertokens" - -func main() { - supertokens.Init(supertokens.TypeInput{ - // highlight-start - Supertokens: &supertokens.ConnectionInfo{ - ConnectionURI: "http://localhost:3567", - APIKey: "someKey", - }, - // highlight-end - }) -} - -``` - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig - -init( - app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), - # highlight-start - supertokens_config=SupertokensConfig( - connection_uri='http://localhost:3567', - api_key='someKey' - ), - # highlight-end - framework='...', # type: ignore - recipe_list=[ - #... - ] -) -``` - - - - -:::tip Security -There is no API key by default. Visit the "Auth flow customization" -> "SuperTokens core settings" -> "Adding API Keys" section to see how to add one. -::: - -## Docker compose file - - - - - - -```bash -version: '3' - -services: - db: - image: mysql:latest - environment: - MYSQL_ROOT_PASSWORD: root - MYSQL_USER: supertokens_user - MYSQL_PASSWORD: somePassword - MYSQL_DATABASE: supertokens - ports: - - 3306:3306 - networks: - - app_network - restart: unless-stopped - healthcheck: - test: [ "CMD", "mysqladmin", "ping", "-h", "localhost" ] - timeout: 20s - retries: 10 - - supertokens: - image: registry.supertokens.io/supertokens/supertokens-mysql^{docker_version_mysql} - depends_on: - db: - condition: service_healthy - ports: - - 3567:3567 - environment: - MYSQL_CONNECTION_URI: mysql://supertokens_user:somePassword@db:3306/supertokens - networks: - - app_network - restart: unless-stopped - healthcheck: - test: > - bash -c 'exec 3<>/dev/tcp/127.0.0.1/3567 && echo -e "GET /hello HTTP/1.1\r\nhost: 127.0.0.1:3567\r\nConnection: close\r\n\r\n" >&3 && cat <&3 | grep "Hello"' - interval: 10s - timeout: 5s - retries: 5 - -networks: - app_network: - driver: bridge -``` - - - - - -```bash -version: '3' - -services: - # Note: If you are assigning a custom name to your db service on the line below, make sure it does not contain underscores - db: - image: 'postgres:latest' - environment: - POSTGRES_USER: supertokens_user - POSTGRES_PASSWORD: somePassword - POSTGRES_DB: supertokens - ports: - - 5432:5432 - networks: - - app_network - restart: unless-stopped - healthcheck: - test: ['CMD', 'pg_isready', '-U', 'supertokens_user', '-d', 'supertokens'] - interval: 5s - timeout: 5s - retries: 5 - - supertokens: - image: registry.supertokens.io/supertokens/supertokens-postgresql^{docker_version_postgresql} - depends_on: - db: - condition: service_healthy - ports: - - 3567:3567 - environment: - POSTGRESQL_CONNECTION_URI: "postgresql://supertokens_user:somePassword@db:5432/supertokens" - networks: - - app_network - restart: unless-stopped - healthcheck: - test: > - bash -c 'exec 3<>/dev/tcp/127.0.0.1/3567 && echo -e "GET /hello HTTP/1.1\r\nhost: 127.0.0.1:3567\r\nConnection: close\r\n\r\n" >&3 && cat <&3 | grep "Hello"' - interval: 10s - timeout: 5s - retries: 5 - -networks: - app_network: - driver: bridge -``` - - - - - - -We are working on adding this section. - - - - - - -:::important -If you are running the backend process that integrates with our backend sdk as part of the docker compose file as well, make sure to use `http://supertokens:3567` as the connection uri instead of `http://localhost:3567`. -::: - - -## Helm charts for Kubernetes - -- For [MySQL image](https://github.com/supertokens/supertokens-docker-mysql/tree/master/helm-chart) - -- For [PostgreSQL image](https://github.com/supertokens/supertokens-docker-postgresql/tree/master/helm-chart) diff --git a/v2/emailpassword/custom-ui/init/core/self-hosted-without-docker.mdx b/v2/emailpassword/custom-ui/init/core/self-hosted-without-docker.mdx index 9a44a4e8c..7068a55cb 100644 --- a/v2/emailpassword/custom-ui/init/core/self-hosted-without-docker.mdx +++ b/v2/emailpassword/custom-ui/init/core/self-hosted-without-docker.mdx @@ -4,165 +4,9 @@ title: Without Docker hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import OSTabs from "/src/components/tabs/OSTabs"; -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -# Binary Installation + -## 1) Download SuperTokens -- Visit the [open source download page](https://supertokens.com/use-oss). -- Click on the "Binary" tab. -- Choose your database. -- Download the SuperTokens zip file for your OS. - -Once downloaded, extract the zip, and you will see a folder named `supertokens`. - -## 2) Install SuperTokens - - - - -```bash -# sudo is required so that the supertokens -# command can be added to your PATH variable. - -cd supertokens -sudo ./install -``` - - - - -```bash - -cd supertokens -./install - -``` -:::caution -You may get an error like `java cannot be opened because the developer cannot be verified`. To solve this, visit System Preferences > Security & Privacy > General Tab, and then click on the Allow button at the bottom. Then retry the command above. -::: - - - - - -```batch - -Rem run as an Administrator. This is required so that the supertokens -Rem command can be added to your PATH. - -cd supertokens -install.bat - -``` - - - -:::important -After installing, you can delete the downloaded folder as you no longer need it. - -Any changes to the the config will be done in the `config.yaml` file in the installation directory, the location of which is specified in the output of the `supertokens --help` command. -::: - -## 3) Start SuperTokens 🚀 -Running the following command will start the service. -```bash -supertokens start [--host=...] [--port=...] -``` -- The above command will start the container with an in-memory database. -- When you are ready to connect it to your database, please visit the [Database setup section](../database-setup/mysql) -- To see all available options please run `supertokens start --help` - - -## 4) Testing that the service is running 🤞 -Open a browser and visit `http://localhost:3567/hello`. If you see a page that says `Hello` back, then SuperTokens was started successfully! - -If you are having issues with starting the docker image, please feel free to reach out to us [over email](mailto:team@supertokens.com) or [via Discord](https://supertokens.com/discord). - - -## 5) Stopping SuperTokens 🛑 -```bash -supertokens stop -``` - -## Connecting the backend SDK with SuperTokens 🔌 -- The default `host` and `port` for SuperTokens is `localhost:3567`. You can change this by passing `--host` and `--port` options to the `start` command. -- The connection info will go in the `supertokens` object in the `init` function on your backend: - - - - - -```tsx -import supertokens from "supertokens-node"; - -supertokens.init({ - // highlight-start - supertokens: { - connectionURI: "http://localhost:3567", - apiKey: "someKey" // OR can be undefined - }, - // highlight-end - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "..." - }, - recipeList: [] -}); - -``` - - - - -```go -import "github.com/supertokens/supertokens-golang/supertokens" - -func main() { - supertokens.Init(supertokens.TypeInput{ - // highlight-start - Supertokens: &supertokens.ConnectionInfo{ - ConnectionURI: "http://localhost:3567", - APIKey: "someKey", - }, - // highlight-end - }) -} - -``` - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig - -init( - app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), - # highlight-start - supertokens_config=SupertokensConfig( - connection_uri='http://localhost:3567', - api_key='someKey' - ), - # highlight-end - framework='...', # type: ignore - recipe_list=[ - #... - ] -) -``` - - - - -:::tip Security -There is no API key by default. Visit the "Auth flow customization" -> "SuperTokens core settings" -> "Adding API Keys" section to see how to add one. -::: diff --git a/v2/emailpassword/custom-ui/init/database-setup/mysql.mdx b/v2/emailpassword/custom-ui/init/database-setup/mysql.mdx index eb37456b2..3597e3e64 100644 --- a/v2/emailpassword/custom-ui/init/database-setup/mysql.mdx +++ b/v2/emailpassword/custom-ui/init/database-setup/mysql.mdx @@ -4,533 +4,7 @@ title: If using MySQL hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import SelfHostedTabs from "/src/components/tabs/SelfHostedTabs" -import TabItem from '@theme/TabItem'; + -# MySQL setup - -:::important -This is needed only if you are running the SuperTokens core yourself. - -The minimum required version is **MySQL 5.7**. -::: - -:::tip blog -We also have [a blog post writeup](https://supertokens.com/blog/connect-supertokens-to-database) highlighting all the steps in more detail for different scenarios. -::: - -## 1) Create a database 🛠️ -```sql -CREATE DATABASE supertokens; -``` -You can skip this step if you want SuperTokens to write to your own database. In this case, you will need to provide your database's name as shown in the step below. - -## 2) Connect SuperTokens to your database 🔌 - - - - -:::caution -Host being `localhost` / `127.0.0.1` will not work in a docker image. Instead, please provide the database's local / public hostname or IP address. - -You also need to make the database listen on all the IP's of the local machine. -This can be done by editing the `mysqld.cnf` config file and setting the value of `bind-address` to `0.0.0.0`. -::: - -```bash - -docker run \ - -p 3567:3567 \ -//highlight-next-line - -e MYSQL_CONNECTION_URI="mysql://username:pass@host/dbName" \ - -d registry.supertokens.io/supertokens/supertokens-mysql - -# OR - -docker run \ - -p 3567:3567 \ -//highlight-start - -e MYSQL_USER="username" \ - -e MYSQL_PASSWORD="password" \ - -e MYSQL_HOST="host" \ - -e MYSQL_PORT="3306" \ - -e MYSQL_DATABASE_NAME="supertokens" \ -//highlight-end - -d registry.supertokens.io/supertokens/supertokens-mysql -``` - - - - - - - -```yaml -# You need to add the following to the config.yaml file. -# The file path can be found by running the "supertokens --help" command - -mysql_connection_uri: "mysql://username:pass@host/dbName" - -# OR - -mysql_user: "username" - -mysql_password: "password" - -mysql_host: "host" - -mysql_port: 3306 - -mysql_database_name: "supertokens" -``` - - - - -## 3) Create tables 👩‍💻👨‍💻 - -:::note -This happens automatically, unless you provide a MySQL user that doesn't have table creation permission. -::: - -```sql -CREATE TABLE `apps` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `created_at_time` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`) -); - -CREATE TABLE `tenants` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `created_at_time` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `tenant_configs` ( - `connection_uri_domain` varchar(256) NOT NULL DEFAULT '', - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `core_config` text, - `email_password_enabled` tinyint(1) DEFAULT NULL, - `passwordless_enabled` tinyint(1) DEFAULT NULL, - `third_party_enabled` tinyint(1) DEFAULT NULL, - `is_first_factors_null` tinyint(1) DEFAULT NULL, - PRIMARY KEY (`connection_uri_domain`,`app_id`,`tenant_id`) -); - -CREATE TABLE `tenant_thirdparty_providers` ( - `connection_uri_domain` varchar(256) NOT NULL DEFAULT '', - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `third_party_id` varchar(28) NOT NULL, - `name` varchar(64) DEFAULT NULL, - `authorization_endpoint` text, - `authorization_endpoint_query_params` text, - `token_endpoint` text, - `token_endpoint_body_params` text, - `user_info_endpoint` text, - `user_info_endpoint_query_params` text, - `user_info_endpoint_headers` text, - `jwks_uri` text, - `oidc_discovery_endpoint` text, - `require_email` tinyint(1) DEFAULT NULL, - `user_info_map_from_id_token_payload_user_id` varchar(64) DEFAULT NULL, - `user_info_map_from_id_token_payload_email` varchar(64) DEFAULT NULL, - `user_info_map_from_id_token_payload_email_verified` varchar(64) DEFAULT NULL, - `user_info_map_from_user_info_endpoint_user_id` varchar(64) DEFAULT NULL, - `user_info_map_from_user_info_endpoint_email` varchar(64) DEFAULT NULL, - `user_info_map_from_user_info_endpoint_email_verified` varchar(64) DEFAULT NULL, - PRIMARY KEY (`connection_uri_domain`,`app_id`,`tenant_id`,`third_party_id`), - FOREIGN KEY (`connection_uri_domain`, `app_id`, `tenant_id`) REFERENCES `tenant_configs` (`connection_uri_domain`, `app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE TABLE `tenant_thirdparty_provider_clients` ( - `connection_uri_domain` varchar(256) NOT NULL DEFAULT '', - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `third_party_id` varchar(28) NOT NULL, - `client_type` varchar(64) NOT NULL DEFAULT '', - `client_id` varchar(256) NOT NULL, - `client_secret` text, - `scope` text, - `force_pkce` tinyint(1) DEFAULT NULL, - `additional_config` text, - PRIMARY KEY (`connection_uri_domain`,`app_id`,`tenant_id`,`third_party_id`,`client_type`), - FOREIGN KEY (`connection_uri_domain`, `app_id`, `tenant_id`, `third_party_id`) REFERENCES `tenant_thirdparty_providers` (`connection_uri_domain`, `app_id`, `tenant_id`, `third_party_id`) ON DELETE CASCADE -); - -CREATE TABLE `tenant_first_factors` ( - connection_uri_domain VARCHAR(256) DEFAULT '', - app_id VARCHAR(64) DEFAULT 'public', - tenant_id VARCHAR(64) DEFAULT 'public', - factor_id VARCHAR(128), - PRIMARY KEY (`connection_uri_domain`, `app_id`, `tenant_id`, `factor_id`), - FOREIGN KEY (`connection_uri_domain`, `app_id`, `tenant_id`) REFERENCES `tenant_configs` (`connection_uri_domain`, `app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE TABLE `tenant_required_secondary_factors` ( - connection_uri_domain VARCHAR(256) DEFAULT '', - app_id VARCHAR(64) DEFAULT 'public', - tenant_id VARCHAR(64) DEFAULT 'public', - factor_id VARCHAR(128), - PRIMARY KEY (`connection_uri_domain`, `app_id`, `tenant_id`, `factor_id`), - FOREIGN KEY (`connection_uri_domain`, `app_id`, `tenant_id`) REFERENCES `tenant_configs` (`connection_uri_domain`, `app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE TABLE `key_value` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `name` varchar(128) NOT NULL, - `value` text, - `created_at_time` bigint unsigned DEFAULT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`name`), - FOREIGN KEY (`app_id`, `tenant_id`) REFERENCES `tenants` (`app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE TABLE `app_id_to_user_id` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `recipe_id` varchar(128) NOT NULL, - `primary_or_recipe_user_id` char(36) NOT NULL, - `is_linked_or_is_a_primary_user` BOOLEAN NOT NULL DEFAULT FALSE, - PRIMARY KEY (`app_id`,`user_id`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE, - FOREIGN KEY (`app_id`, `primary_or_recipe_user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE INDEX app_id_to_user_id_primary_user_id_index ON `app_id_to_user_id` (`primary_or_recipe_user_id`); - -CREATE INDEX app_id_to_user_id_user_id_index ON `app_id_to_user_id` (`user_id`); - -CREATE TABLE `all_auth_recipe_users` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `primary_or_recipe_user_id` char(36) NOT NULL, - `is_linked_or_is_a_primary_user` BOOLEAN NOT NULL DEFAULT FALSE, - `recipe_id` varchar(128) NOT NULL, - `time_joined` bigint unsigned NOT NULL, - `primary_or_recipe_user_time_joined` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`user_id`), - KEY `app_id` (`app_id`,`user_id`), - FOREIGN KEY (`app_id`, `tenant_id`) REFERENCES `tenants` (`app_id`, `tenant_id`) ON DELETE CASCADE, - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE, - FOREIGN KEY (`app_id`, `primary_or_recipe_user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE INDEX all_auth_recipe_users_pagination_index1 ON all_auth_recipe_users - (app_id, tenant_id, primary_or_recipe_user_time_joined DESC, primary_or_recipe_user_id DESC); - -CREATE INDEX all_auth_recipe_users_pagination_index3 ON all_auth_recipe_users - (recipe_id, app_id, tenant_id, primary_or_recipe_user_time_joined DESC, primary_or_recipe_user_id DESC); - -CREATE INDEX all_auth_recipe_users_primary_user_id_index ON all_auth_recipe_users - (primary_or_recipe_user_id, app_id); - -CREATE INDEX all_auth_recipe_users_recipe_id_index ON all_auth_recipe_users - (app_id, recipe_id, tenant_id); - -CREATE TABLE `userid_mapping` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `supertokens_user_id` char(36) NOT NULL, - `external_user_id` varchar(128) NOT NULL, - `external_user_id_info` text, - PRIMARY KEY (`app_id`,`supertokens_user_id`,`external_user_id`), - UNIQUE KEY `supertokens_user_id` (`app_id`,`supertokens_user_id`), - UNIQUE KEY `external_user_id` (`app_id`,`external_user_id`), - FOREIGN KEY (`app_id`, `supertokens_user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE TABLE `dashboard_users` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `email` varchar(256) NOT NULL, - `password_hash` varchar(256) NOT NULL, - `time_joined` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`user_id`), - UNIQUE KEY `email` (`app_id`,`email`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `dashboard_user_sessions` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `session_id` char(36) NOT NULL, - `user_id` char(36) NOT NULL, - `time_created` bigint unsigned NOT NULL, - `expiry` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`session_id`), - KEY `app_id` (`app_id`,`user_id`), - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `dashboard_users` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE INDEX `dashboard_user_sessions_expiry_index` ON `dashboard_user_sessions` (`expiry`); - -CREATE TABLE `session_access_token_signing_keys` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `created_at_time` bigint unsigned NOT NULL, - `value` text, - PRIMARY KEY (`app_id`,`created_at_time`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `session_info` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `session_handle` varchar(255) NOT NULL, - `user_id` varchar(128) NOT NULL, - `refresh_token_hash_2` varchar(128) NOT NULL, - `session_data` text, - `expires_at` bigint unsigned NOT NULL, - `created_at_time` bigint unsigned NOT NULL, - `jwt_user_payload` text, - `use_static_key` tinyint(1) NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`session_handle`), - FOREIGN KEY (`app_id`, `tenant_id`) REFERENCES `tenants` (`app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE INDEX `session_expiry_index` ON `session_info` (`expires_at`); - -CREATE TABLE `user_last_active` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - `last_active_time` bigint unsigned DEFAULT NULL, - PRIMARY KEY (`app_id`,`user_id`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `emailpassword_users` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `email` varchar(256) NOT NULL, - `password_hash` varchar(256) NOT NULL, - `time_joined` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`user_id`), - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE TABLE `emailpassword_user_to_tenant` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `email` varchar(256) NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`user_id`), - UNIQUE KEY `email` (`app_id`,`tenant_id`,`email`), - FOREIGN KEY (`app_id`, `tenant_id`, `user_id`) REFERENCES `all_auth_recipe_users` (`app_id`, `tenant_id`, `user_id`) ON DELETE CASCADE -); - -CREATE TABLE `emailpassword_pswd_reset_tokens` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `token` varchar(128) NOT NULL, - `token_expiry` bigint unsigned NOT NULL, - `email` varchar(256), - PRIMARY KEY (`app_id`,`user_id`,`token`), - UNIQUE KEY `token` (`token`), - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE ON UPDATE CASCADE -); - -CREATE INDEX `emailpassword_password_reset_token_expiry_index` ON `emailpassword_pswd_reset_tokens` (`token_expiry`); - -CREATE TABLE `emailverification_verified_emails` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - `email` varchar(256) NOT NULL, - PRIMARY KEY (`app_id`,`user_id`,`email`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `emailverification_tokens` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - `email` varchar(256) NOT NULL, - `token` varchar(128) NOT NULL, - `token_expiry` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`user_id`,`email`,`token`), - UNIQUE KEY `token` (`token`), - FOREIGN KEY (`app_id`, `tenant_id`) REFERENCES `tenants` (`app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE INDEX `emailverification_tokens_index` ON `emailverification_tokens` (`token_expiry`); - -CREATE TABLE `thirdparty_users` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `third_party_id` varchar(28) NOT NULL, - `third_party_user_id` varchar(256) NOT NULL, - `user_id` char(36) NOT NULL, - `email` varchar(256) NOT NULL, - `time_joined` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`user_id`), - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE INDEX `thirdparty_users_email_index` ON `thirdparty_users` (`app_id`,`email`); - -CREATE INDEX `thirdparty_users_thirdparty_user_id_index` ON `thirdparty_users` (`app_id`,`third_party_id`,`third_party_user_id`); - -CREATE TABLE `thirdparty_user_to_tenant` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `third_party_id` varchar(28) NOT NULL, - `third_party_user_id` varchar(256) NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`user_id`), - UNIQUE KEY `third_party_user_id` (`app_id`,`tenant_id`,`third_party_id`,`third_party_user_id`), - FOREIGN KEY (`app_id`, `tenant_id`, `user_id`) REFERENCES `all_auth_recipe_users` (`app_id`, `tenant_id`, `user_id`) ON DELETE CASCADE -); - -CREATE TABLE `passwordless_users` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `email` varchar(256) DEFAULT NULL, - `phone_number` varchar(256) DEFAULT NULL, - `time_joined` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`user_id`), - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE TABLE `passwordless_user_to_tenant` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `email` varchar(256) DEFAULT NULL, - `phone_number` varchar(256) DEFAULT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`user_id`), - UNIQUE KEY `email` (`app_id`,`tenant_id`,`email`), - UNIQUE KEY `phone_number` (`app_id`,`tenant_id`,`phone_number`), - FOREIGN KEY (`app_id`, `tenant_id`, `user_id`) REFERENCES `all_auth_recipe_users` (`app_id`, `tenant_id`, `user_id`) ON DELETE CASCADE -); - -CREATE TABLE `passwordless_devices` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `device_id_hash` char(44) NOT NULL, - `email` varchar(256) DEFAULT NULL, - `phone_number` varchar(256) DEFAULT NULL, - `link_code_salt` char(44) NOT NULL, - `failed_attempts` int unsigned NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`device_id_hash`), - FOREIGN KEY (`app_id`, `tenant_id`) REFERENCES `tenants` (`app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE INDEX `passwordless_devices_email_index` ON `passwordless_devices` (`app_id`,`tenant_id`,`email`); - -CREATE INDEX `passwordless_devices_phone_number_index` ON `passwordless_devices` (`app_id`,`tenant_id`,`phone_number`); - -CREATE TABLE `passwordless_codes` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `code_id` char(36) NOT NULL, - `device_id_hash` char(44) NOT NULL, - `link_code_hash` char(44) NOT NULL, - `created_at` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`code_id`), - UNIQUE KEY `link_code_hash` (`app_id`,`tenant_id`,`link_code_hash`), - KEY `app_id` (`app_id`,`tenant_id`,`device_id_hash`), - FOREIGN KEY (`app_id`, `tenant_id`, `device_id_hash`) REFERENCES `passwordless_devices` (`app_id`, `tenant_id`, `device_id_hash`) ON DELETE CASCADE ON UPDATE CASCADE -); - -CREATE INDEX `passwordless_codes_created_at_index` ON `passwordless_codes` (`app_id`,`tenant_id`,`created_at`); - -CREATE TABLE `jwt_signing_keys` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `key_id` varchar(255) NOT NULL, - `key_string` text NOT NULL, - `algorithm` varchar(10) NOT NULL, - `created_at` bigint unsigned DEFAULT NULL, - PRIMARY KEY (`app_id`,`key_id`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `user_metadata` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - `user_metadata` text NOT NULL, - PRIMARY KEY (`app_id`,`user_id`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `roles` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `role` varchar(255) NOT NULL, - PRIMARY KEY (`app_id`,`role`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `role_permissions` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `role` varchar(255) NOT NULL, - `permission` varchar(255) NOT NULL, - PRIMARY KEY (`app_id`,`role`,`permission`), - FOREIGN KEY (`app_id`, `role`) REFERENCES `roles` (`app_id`, `role`) ON DELETE CASCADE -); - -CREATE INDEX `role_permissions_permission_index` ON `role_permissions` (`app_id`,`permission`); - -CREATE TABLE `user_roles` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - `role` varchar(255) NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`user_id`,`role`), - FOREIGN KEY (`app_id`, `tenant_id`) REFERENCES `tenants` (`app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE INDEX `user_roles_role_index` ON `user_roles` (`app_id`,`tenant_id`,`role`); - -CREATE TABLE `totp_users` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - PRIMARY KEY (`app_id`,`user_id`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `totp_user_devices` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - `device_name` varchar(256) NOT NULL, - `secret_key` varchar(256) NOT NULL, - `period` int NOT NULL, - `skew` int NOT NULL, - `verified` tinyint(1) NOT NULL, - `created_at` BIGINT UNSIGNED, - PRIMARY KEY (`app_id`,`user_id`,`device_name`), - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `totp_users` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE TABLE `totp_used_codes` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - `code` varchar(8) NOT NULL, - `is_valid` tinyint(1) NOT NULL, - `expiry_time_ms` bigint unsigned NOT NULL, - `created_time_ms` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`user_id`,`created_time_ms`), - KEY `app_id` (`app_id`,`user_id`), - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `totp_users` (`app_id`, `user_id`) ON DELETE CASCADE, - FOREIGN KEY (`app_id`, `tenant_id`) REFERENCES `tenants` (`app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE INDEX `totp_used_codes_expiry_time_ms_index` ON `totp_used_codes` (`app_id`,`tenant_id`,`expiry_time_ms`); -``` - -:::tip -You also have the option to [rename these tables](./rename-database-tables). -::: - -## 4) Test the connection 🤞 -To test, start SuperTokens and run the following query in your database -```sql -SELECT * FROM key_value; -``` -If you see at least one row, it means that the connection has been successfully completed! 🥳🎉 - -:::tip blog -We also have [a blog post writeup](https://supertokens.com/blog/connect-supertokens-to-database) highlighting all the steps in more detail for different scenarios. -::: diff --git a/v2/emailpassword/custom-ui/init/database-setup/postgresql.mdx b/v2/emailpassword/custom-ui/init/database-setup/postgresql.mdx index abaea10fc..8375aa15f 100644 --- a/v2/emailpassword/custom-ui/init/database-setup/postgresql.mdx +++ b/v2/emailpassword/custom-ui/init/database-setup/postgresql.mdx @@ -4,601 +4,8 @@ title: If using PostgreSQL hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import SelfHostedTabs from "/src/components/tabs/SelfHostedTabs" -import TabItem from '@theme/TabItem'; -# PostgreSQL setup + -:::important -This is needed only if you are running the SuperTokens core yourself. - -The minimum required version is **PostgreSQL 9.6**. -::: - -:::tip blog -We also have [a blog post writeup](https://supertokens.com/blog/connect-supertokens-to-database) highlighting all the steps in more detail for different scenarios. -::: - -## 1) Create a database 🛠️ - -```sql -CREATE DATABASE supertokens; -``` -You can skip this step if you want SuperTokens to write to your own database. In this case, you will need to provide your database name as shown in the step below. - -## 2) Connect SuperTokens to your database 🔌 - - - - -:::caution -Host being `localhost` / `127.0.0.1` will not work in a docker image. Instead, please provide the database's local / public hostname or IP address. - -You also need to make the database listen on all the IP's of the local machine. -This can be done by editing the `postgresql.conf` config file and setting the value of `listen_addresses` to `0.0.0.0`. -::: - -```bash - -docker run \ - -p 3567:3567 \ - // highlight-next-line - -e POSTGRESQL_CONNECTION_URI="postgresql://username:pass@host/dbName" \ - -d registry.supertokens.io/supertokens/supertokens-postgresql - -# OR - -docker run \ - -p 3567:3567 \ - // highlight-start - -e POSTGRESQL_USER="username" \ - -e POSTGRESQL_PASSWORD="password" \ - -e POSTGRESQL_HOST="host" \ - -e POSTGRESQL_PORT="5432" \ - -e POSTGRESQL_DATABASE_NAME="supertokens" \ - // highlight-end - -d registry.supertokens.io/supertokens/supertokens-postgresql -``` - -:::tip -You can also provide the table schema by providing the `POSTGRESQL_TABLE_SCHEMA` option. -::: - - - - - -```yaml -# You need to add the following to the config.yaml file. -# The file path can be found by running the "supertokens --help" command - -postgresql_connection_uri: "postgresql://username:pass@host/dbName" - -# OR - -postgresql_user: "username" - -postgresql_password: "password" - -postgresql_host: "host" - -postgresql_port: "5432" - -postgresql_database_name: "supertokens" -``` - -- You can also provide the table schema by providing the `postgresql_table_schema` option. - - - - - -## 3) Create tables 👩‍💻👨‍💻 - -:::note -This happens automatically, unless you provide a PostgreSQL user that doesn't have table creation permission. -::: - -```sql -CREATE TABLE apps ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - created_at_time BIGINT, - CONSTRAINT apps_pkey PRIMARY KEY (app_id) -); - -CREATE TABLE tenants ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - created_at_time BIGINT, - CONSTRAINT tenants_pkey PRIMARY KEY (app_id, tenant_id), - CONSTRAINT tenants_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX tenants_app_id_index ON tenants (app_id); - -CREATE TABLE tenant_configs ( - connection_uri_domain VARCHAR(256) DEFAULT '' NOT NULL, - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - core_config TEXT, - email_password_enabled BOOLEAN, - passwordless_enabled BOOLEAN, - third_party_enabled BOOLEAN, - is_first_factors_null BOOLEAN, - CONSTRAINT tenant_configs_pkey PRIMARY KEY (connection_uri_domain, app_id, tenant_id) -); - -CREATE TABLE tenant_thirdparty_providers ( - connection_uri_domain VARCHAR(256) DEFAULT '' NOT NULL, - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - third_party_id VARCHAR(28) NOT NULL, - name VARCHAR(64), - authorization_endpoint TEXT, - authorization_endpoint_query_params TEXT, - token_endpoint TEXT, - token_endpoint_body_params TEXT, - user_info_endpoint TEXT, - user_info_endpoint_query_params TEXT, - user_info_endpoint_headers TEXT, - jwks_uri TEXT, - oidc_discovery_endpoint TEXT, - require_email BOOLEAN, - user_info_map_from_id_token_payload_user_id VARCHAR(64), - user_info_map_from_id_token_payload_email VARCHAR(64), - user_info_map_from_id_token_payload_email_verified VARCHAR(64), - user_info_map_from_user_info_endpoint_user_id VARCHAR(64), - user_info_map_from_user_info_endpoint_email VARCHAR(64), - user_info_map_from_user_info_endpoint_email_verified VARCHAR(64), - CONSTRAINT tenant_thirdparty_providers_pkey PRIMARY KEY (connection_uri_domain, app_id, tenant_id, third_party_id), - CONSTRAINT tenant_thirdparty_providers_tenant_id_fkey FOREIGN KEY (connection_uri_domain, app_id, tenant_id) REFERENCES public.tenant_configs(connection_uri_domain, app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX tenant_thirdparty_providers_tenant_id_index ON tenant_thirdparty_providers (connection_uri_domain, app_id, tenant_id); - -CREATE TABLE tenant_thirdparty_provider_clients ( - connection_uri_domain VARCHAR(256) DEFAULT '' NOT NULL, - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - third_party_id VARCHAR(28) NOT NULL, - client_type VARCHAR(64) DEFAULT '' NOT NULL, - client_id VARCHAR(256) NOT NULL, - client_secret TEXT, - scope VARCHAR(128)[], - force_pkce BOOLEAN, - additional_config TEXT, - CONSTRAINT tenant_thirdparty_provider_clients_pkey PRIMARY KEY (connection_uri_domain, app_id, tenant_id, third_party_id, client_type), - CONSTRAINT tenant_thirdparty_provider_clients_third_party_id_fkey FOREIGN KEY (connection_uri_domain, app_id, tenant_id, third_party_id) REFERENCES public.tenant_thirdparty_providers(connection_uri_domain, app_id, tenant_id, third_party_id) ON DELETE CASCADE -); - -CREATE INDEX tenant_thirdparty_provider_clients_third_party_id_index ON tenant_thirdparty_provider_clients (connection_uri_domain, app_id, tenant_id, third_party_id); - -CREATE TABLE tenant_first_factors ( - connection_uri_domain VARCHAR(256) DEFAULT '' NOT NULL, - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - factor_id VARCHAR(128), - CONSTRAINT tenant_first_factors_pkey PRIMARY KEY (connection_uri_domain, app_id, tenant_id, factor_id), - CONSTRAINT tenant_first_factors_tenant_id_fkey FOREIGN KEY (connection_uri_domain, app_id, tenant_id) REFERENCES public.tenant_configs(connection_uri_domain, app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX IF NOT EXISTS tenant_first_factors_tenant_id_index ON tenant_first_factors (connection_uri_domain, app_id, tenant_id); - -CREATE TABLE tenant_required_secondary_factors ( - connection_uri_domain VARCHAR(256) DEFAULT '' NOT NULL, - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - factor_id VARCHAR(128), - CONSTRAINT tenant_required_secondary_factors_pkey PRIMARY KEY (connection_uri_domain, app_id, tenant_id, factor_id), - CONSTRAINT tenant_required_secondary_factors_tenant_id_fkey FOREIGN KEY (connection_uri_domain, app_id, tenant_id) REFERENCES public.tenant_configs(connection_uri_domain, app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX IF NOT EXISTS tenant_default_required_factor_ids_tenant_id_index ON tenant_required_secondary_factors (connection_uri_domain, app_id, tenant_id); - -CREATE TABLE key_value ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - name VARCHAR(128) NOT NULL, - value TEXT, - created_at_time BIGINT, - CONSTRAINT key_value_pkey PRIMARY KEY (app_id, tenant_id, name), - CONSTRAINT key_value_tenant_id_fkey FOREIGN KEY (app_id, tenant_id) REFERENCES public.tenants(app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX key_value_tenant_id_index ON key_value (app_id, tenant_id); - -CREATE TABLE app_id_to_user_id ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - recipe_id VARCHAR(128) NOT NULL, - primary_or_recipe_user_id CHAR(36) NOT NULL, - is_linked_or_is_a_primary_user BOOLEAN NOT NULL DEFAULT FALSE, - CONSTRAINT app_id_to_user_id_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT app_id_to_user_id_primary_or_recipe_user_id_fkey FOREIGN KEY(app_id, primary_or_recipe_user_id) REFERENCES app_id_to_user_id (app_id, user_id) ON DELETE CASCADE, - CONSTRAINT app_id_to_user_id_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX app_id_to_user_id_app_id_index ON app_id_to_user_id (app_id); - -CREATE INDEX app_id_to_user_id_primary_user_id_index ON app_id_to_user_id (primary_or_recipe_user_id, app_id); - -CREATE TABLE all_auth_recipe_users ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - primary_or_recipe_user_id CHAR(36) NOT NULL, - is_linked_or_is_a_primary_user BOOLEAN NOT NULL DEFAULT FALSE, - recipe_id VARCHAR(128) NOT NULL, - time_joined BIGINT NOT NULL, - primary_or_recipe_user_time_joined BIGINT NOT NULL, - CONSTRAINT all_auth_recipe_users_pkey PRIMARY KEY (app_id, tenant_id, user_id), - CONSTRAINT all_auth_recipe_users_tenant_id_fkey FOREIGN KEY (app_id, tenant_id) REFERENCES public.tenants(app_id, tenant_id) ON DELETE CASCADE, - CONSTRAINT all_auth_recipe_users_primary_or_recipe_user_id_fkey FOREIGN KEY(app_id, primary_or_recipe_user_id) REFERENCES public.app_id_to_user_id (app_id, user_id) ON DELETE CASCADE, - CONSTRAINT all_auth_recipe_users_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.app_id_to_user_id(app_id, user_id) ON DELETE CASCADE -); - -CREATE INDEX all_auth_recipe_users_pagination_index1 ON all_auth_recipe_users - (app_id, tenant_id, primary_or_recipe_user_time_joined DESC, primary_or_recipe_user_id DESC); - -CREATE INDEX all_auth_recipe_users_pagination_index2 ON all_auth_recipe_users - (app_id, tenant_id, primary_or_recipe_user_time_joined ASC, primary_or_recipe_user_id DESC); - -CREATE INDEX all_auth_recipe_users_pagination_index3 ON all_auth_recipe_users - (recipe_id, app_id, tenant_id, primary_or_recipe_user_time_joined DESC, primary_or_recipe_user_id DESC); - - -CREATE INDEX all_auth_recipe_users_pagination_index4 ON all_auth_recipe_users - (recipe_id, app_id, tenant_id, primary_or_recipe_user_time_joined ASC, primary_or_recipe_user_id DESC); - - -CREATE INDEX all_auth_recipe_users_primary_user_id_index ON all_auth_recipe_users - (primary_or_recipe_user_id, app_id); - -CREATE INDEX all_auth_recipe_users_recipe_id_index ON all_auth_recipe_users - (app_id, recipe_id, tenant_id); - -CREATE INDEX all_auth_recipe_user_id_index ON all_auth_recipe_users (app_id, user_id); - -CREATE INDEX all_auth_recipe_tenant_id_index ON all_auth_recipe_users (app_id, tenant_id); - -CREATE TABLE userid_mapping ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - supertokens_user_id character(36) NOT NULL, - external_user_id VARCHAR(128) NOT NULL, - external_user_id_info TEXT, - CONSTRAINT userid_mapping_external_user_id_key UNIQUE (app_id, external_user_id), - CONSTRAINT userid_mapping_pkey PRIMARY KEY (app_id, supertokens_user_id, external_user_id), - CONSTRAINT userid_mapping_supertokens_user_id_key UNIQUE (app_id, supertokens_user_id), - CONSTRAINT userid_mapping_supertokens_user_id_fkey FOREIGN KEY (app_id, supertokens_user_id) REFERENCES public.app_id_to_user_id(app_id, user_id) ON DELETE CASCADE -); - -CREATE INDEX userid_mapping_supertokens_user_id_index ON userid_mapping (app_id, supertokens_user_id); - -CREATE TABLE dashboard_users ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - email VARCHAR(256) NOT NULL, - password_hash VARCHAR(256) NOT NULL, - time_joined BIGINT NOT NULL, - CONSTRAINT dashboard_users_email_key UNIQUE (app_id, email), - CONSTRAINT dashboard_users_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT dashboard_users_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX dashboard_users_app_id_index ON dashboard_users (app_id); - -CREATE TABLE dashboard_user_sessions ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - session_id character(36) NOT NULL, - user_id character(36) NOT NULL, - time_created BIGINT NOT NULL, - expiry BIGINT NOT NULL, - CONSTRAINT dashboard_user_sessions_pkey PRIMARY KEY (app_id, session_id), - CONSTRAINT dashboard_user_sessions_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.dashboard_users(app_id, user_id) ON UPDATE CASCADE ON DELETE CASCADE -); - -CREATE INDEX dashboard_user_sessions_expiry_index ON dashboard_user_sessions (expiry); - -CREATE INDEX dashboard_user_sessions_user_id_index ON dashboard_user_sessions (app_id, user_id); - -CREATE TABLE session_access_token_signing_keys ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - created_at_time BIGINT NOT NULL, - value TEXT, - CONSTRAINT session_access_token_signing_keys_pkey PRIMARY KEY (app_id, created_at_time), - CONSTRAINT session_access_token_signing_keys_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX access_token_signing_keys_app_id_index ON session_access_token_signing_keys (app_id); - -CREATE TABLE session_info ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - session_handle VARCHAR(255) NOT NULL, - user_id VARCHAR(128) NOT NULL, - refresh_token_hash_2 VARCHAR(128) NOT NULL, - session_data TEXT, - expires_at BIGINT NOT NULL, - created_at_time BIGINT NOT NULL, - jwt_user_payload TEXT, - use_static_key BOOLEAN NOT NULL, - CONSTRAINT session_info_pkey PRIMARY KEY (app_id, tenant_id, session_handle), - CONSTRAINT session_info_tenant_id_fkey FOREIGN KEY (app_id, tenant_id) REFERENCES public.tenants(app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX session_expiry_index ON session_info (expires_at); - -CREATE INDEX session_info_tenant_id_index ON session_info (app_id, tenant_id); - -CREATE TABLE user_last_active ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - last_active_time BIGINT, - CONSTRAINT user_last_active_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT user_last_active_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX user_last_active_app_id_index ON user_last_active (app_id); - -CREATE TABLE emailpassword_users ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - email VARCHAR(256) NOT NULL, - password_hash VARCHAR(256) NOT NULL, - time_joined BIGINT NOT NULL, - CONSTRAINT emailpassword_users_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT emailpassword_users_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.app_id_to_user_id(app_id, user_id) ON DELETE CASCADE -); - -CREATE TABLE emailpassword_user_to_tenant ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - email VARCHAR(256) NOT NULL, - CONSTRAINT emailpassword_user_to_tenant_email_key UNIQUE (app_id, tenant_id, email), - CONSTRAINT emailpassword_user_to_tenant_pkey PRIMARY KEY (app_id, tenant_id, user_id), - CONSTRAINT emailpassword_user_to_tenant_user_id_fkey FOREIGN KEY (app_id, tenant_id, user_id) REFERENCES public.all_auth_recipe_users(app_id, tenant_id, user_id) ON DELETE CASCADE -); - -CREATE TABLE emailpassword_pswd_reset_tokens ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - token VARCHAR(128) NOT NULL, - token_expiry BIGINT NOT NULL, - email VARCHAR(256), - CONSTRAINT emailpassword_pswd_reset_tokens_pkey PRIMARY KEY (app_id, user_id, token), - CONSTRAINT emailpassword_pswd_reset_tokens_token_key UNIQUE (token), - CONSTRAINT emailpassword_pswd_reset_tokens_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.app_id_to_user_id(app_id, user_id) ON UPDATE CASCADE ON DELETE CASCADE -); - -CREATE INDEX emailpassword_password_reset_token_expiry_index ON emailpassword_pswd_reset_tokens (token_expiry); - -CREATE INDEX emailpassword_pswd_reset_tokens_user_id_index ON emailpassword_pswd_reset_tokens (app_id, user_id); - -CREATE TABLE emailverification_verified_emails ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - email VARCHAR(256) NOT NULL, - CONSTRAINT emailverification_verified_emails_pkey PRIMARY KEY (app_id, user_id, email), - CONSTRAINT emailverification_verified_emails_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX emailverification_verified_emails_app_id_index ON emailverification_verified_emails (app_id); - -CREATE TABLE emailverification_tokens ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - email VARCHAR(256) NOT NULL, - token VARCHAR(128) NOT NULL, - token_expiry BIGINT NOT NULL, - CONSTRAINT emailverification_tokens_pkey PRIMARY KEY (app_id, tenant_id, user_id, email, token), - CONSTRAINT emailverification_tokens_token_key UNIQUE (token), - CONSTRAINT emailverification_tokens_tenant_id_fkey FOREIGN KEY (app_id, tenant_id) REFERENCES public.tenants(app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX emailverification_tokens_index ON emailverification_tokens (token_expiry); - -CREATE INDEX emailverification_tokens_tenant_id_index ON emailverification_tokens (app_id, tenant_id); - -CREATE TABLE thirdparty_users ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - third_party_id VARCHAR(28) NOT NULL, - third_party_user_id VARCHAR(256) NOT NULL, - user_id character(36) NOT NULL, - email VARCHAR(256) NOT NULL, - time_joined BIGINT NOT NULL, - CONSTRAINT thirdparty_users_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT thirdparty_users_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.app_id_to_user_id(app_id, user_id) ON DELETE CASCADE -); - -CREATE INDEX thirdparty_users_email_index ON thirdparty_users (app_id, email); - -CREATE INDEX thirdparty_users_thirdparty_user_id_index ON thirdparty_users (app_id, third_party_id, third_party_user_id); - -CREATE TABLE thirdparty_user_to_tenant ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - third_party_id VARCHAR(28) NOT NULL, - third_party_user_id VARCHAR(256) NOT NULL, - CONSTRAINT thirdparty_user_to_tenant_pkey PRIMARY KEY (app_id, tenant_id, user_id), - CONSTRAINT thirdparty_user_to_tenant_third_party_user_id_key UNIQUE (app_id, tenant_id, third_party_id, third_party_user_id), - CONSTRAINT thirdparty_user_to_tenant_user_id_fkey FOREIGN KEY (app_id, tenant_id, user_id) REFERENCES public.all_auth_recipe_users(app_id, tenant_id, user_id) ON DELETE CASCADE -); - -CREATE TABLE passwordless_users ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - email VARCHAR(256), - phone_number VARCHAR(256), - time_joined BIGINT NOT NULL, - CONSTRAINT passwordless_users_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT passwordless_users_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.app_id_to_user_id(app_id, user_id) ON DELETE CASCADE -); - -CREATE TABLE passwordless_user_to_tenant ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - email VARCHAR(256), - phone_number VARCHAR(256), - CONSTRAINT passwordless_user_to_tenant_email_key UNIQUE (app_id, tenant_id, email), - CONSTRAINT passwordless_user_to_tenant_phone_number_key UNIQUE (app_id, tenant_id, phone_number), - CONSTRAINT passwordless_user_to_tenant_pkey PRIMARY KEY (app_id, tenant_id, user_id), - CONSTRAINT passwordless_user_to_tenant_user_id_fkey FOREIGN KEY (app_id, tenant_id, user_id) REFERENCES public.all_auth_recipe_users(app_id, tenant_id, user_id) ON DELETE CASCADE -); - -CREATE TABLE passwordless_devices ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - device_id_hash character(44) NOT NULL, - email VARCHAR(256), - phone_number VARCHAR(256), - link_code_salt character(44) NOT NULL, - failed_attempts integer NOT NULL, - CONSTRAINT passwordless_devices_pkey PRIMARY KEY (app_id, tenant_id, device_id_hash), - CONSTRAINT passwordless_devices_tenant_id_fkey FOREIGN KEY (app_id, tenant_id) REFERENCES public.tenants(app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX passwordless_devices_email_index ON passwordless_devices (app_id, tenant_id, email); - -CREATE INDEX passwordless_devices_phone_number_index ON passwordless_devices (app_id, tenant_id, phone_number); - -CREATE INDEX passwordless_devices_tenant_id_index ON passwordless_devices (app_id, tenant_id); - -CREATE TABLE passwordless_codes ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - code_id character(36) NOT NULL, - device_id_hash character(44) NOT NULL, - link_code_hash character(44) NOT NULL, - created_at BIGINT NOT NULL, - CONSTRAINT passwordless_codes_link_code_hash_key UNIQUE (app_id, tenant_id, link_code_hash), - CONSTRAINT passwordless_codes_pkey PRIMARY KEY (app_id, tenant_id, code_id), - CONSTRAINT passwordless_codes_device_id_hash_fkey FOREIGN KEY (app_id, tenant_id, device_id_hash) REFERENCES public.passwordless_devices(app_id, tenant_id, device_id_hash) ON UPDATE CASCADE ON DELETE CASCADE -); - -CREATE INDEX passwordless_codes_created_at_index ON passwordless_codes (app_id, tenant_id, created_at); - -CREATE INDEX passwordless_codes_device_id_hash_index ON passwordless_codes (app_id, tenant_id, device_id_hash); - -CREATE TABLE jwt_signing_keys ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - key_id VARCHAR(255) NOT NULL, - key_string TEXT NOT NULL, - algorithm VARCHAR(10) NOT NULL, - created_at BIGINT, - CONSTRAINT jwt_signing_keys_pkey PRIMARY KEY (app_id, key_id), - CONSTRAINT jwt_signing_keys_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX jwt_signing_keys_app_id_index ON jwt_signing_keys (app_id); - -CREATE TABLE user_metadata ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - user_metadata TEXT NOT NULL, - CONSTRAINT user_metadata_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT user_metadata_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX user_metadata_app_id_index ON user_metadata (app_id); - -CREATE TABLE roles ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - role VARCHAR(255) NOT NULL, - CONSTRAINT roles_pkey PRIMARY KEY (app_id, role), - CONSTRAINT roles_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX roles_app_id_index ON roles (app_id); - -CREATE TABLE role_permissions ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - role VARCHAR(255) NOT NULL, - permission VARCHAR(255) NOT NULL, - CONSTRAINT role_permissions_pkey PRIMARY KEY (app_id, role, permission), - CONSTRAINT role_permissions_role_fkey FOREIGN KEY (app_id, role) REFERENCES public.roles(app_id, role) ON DELETE CASCADE -); - -CREATE INDEX role_permissions_permission_index ON role_permissions (app_id, permission); - -CREATE INDEX role_permissions_role_index ON role_permissions (app_id, role); - -CREATE TABLE user_roles ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - role VARCHAR(255) NOT NULL, - CONSTRAINT user_roles_pkey PRIMARY KEY (app_id, tenant_id, user_id, role), - CONSTRAINT user_roles_tenant_id_fkey FOREIGN KEY (app_id, tenant_id) REFERENCES public.tenants(app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX user_roles_role_index ON user_roles (app_id, tenant_id, role); - -CREATE INDEX user_roles_tenant_id_index ON user_roles (app_id, tenant_id); - -CREATE INDEX user_roles_app_id_role_index ON user_roles (app_id, role); - -CREATE TABLE totp_users ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - CONSTRAINT totp_users_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT totp_users_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX totp_users_app_id_index ON totp_users (app_id); - -CREATE TABLE totp_user_devices ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - device_name VARCHAR(256) NOT NULL, - secret_key VARCHAR(256) NOT NULL, - period integer NOT NULL, - skew integer NOT NULL, - verified BOOLEAN NOT NULL, - created_at BIGINT, - CONSTRAINT totp_user_devices_pkey PRIMARY KEY (app_id, user_id, device_name), - CONSTRAINT totp_user_devices_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.totp_users(app_id, user_id) ON DELETE CASCADE -); - -CREATE INDEX totp_user_devices_user_id_index ON totp_user_devices (app_id, user_id); - -CREATE TABLE totp_used_codes ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - code VARCHAR(8) NOT NULL, - is_valid BOOLEAN NOT NULL, - expiry_time_ms BIGINT NOT NULL, - created_time_ms BIGINT NOT NULL, - CONSTRAINT totp_used_codes_pkey PRIMARY KEY (app_id, tenant_id, user_id, created_time_ms), - CONSTRAINT totp_used_codes_tenant_id_fkey FOREIGN KEY (app_id, tenant_id) REFERENCES public.tenants(app_id, tenant_id) ON DELETE CASCADE, - CONSTRAINT totp_used_codes_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.totp_users(app_id, user_id) ON DELETE CASCADE -); - -CREATE INDEX totp_used_codes_expiry_time_ms_index ON totp_used_codes (app_id, tenant_id, expiry_time_ms); - -CREATE INDEX totp_used_codes_tenant_id_index ON totp_used_codes (app_id, tenant_id); - -CREATE INDEX totp_used_codes_user_id_index ON totp_used_codes (app_id, user_id); -``` - -:::tip -You also have the option to [rename these tables](./rename-database-tables). -::: - -## 4) Test the connection 🤞 -To test, start SuperTokens and run the following query in your database -```sql -SELECT * FROM key_value; -``` -If you see at least one row, it means that the connection has been successfully completed! 🥳🎉 - -:::tip blog -We also have [a blog post writeup](https://supertokens.com/blog/connect-supertokens-to-database) highlighting all the steps in more detail for different scenarios. -::: diff --git a/v2/emailpassword/custom-ui/init/database-setup/rename-database-tables.mdx b/v2/emailpassword/custom-ui/init/database-setup/rename-database-tables.mdx index 93f84ccc6..e02f73ba3 100644 --- a/v2/emailpassword/custom-ui/init/database-setup/rename-database-tables.mdx +++ b/v2/emailpassword/custom-ui/init/database-setup/rename-database-tables.mdx @@ -4,95 +4,8 @@ title: Rename database tables hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import SelfHostedTabs from "/src/components/tabs/SelfHostedTabs" -import TabItem from '@theme/TabItem'; -# Rename database tables + -:::caution -If you already have tables created by SuperTokens, and then you rename them, SuperTokens will create new tables. So please be sure to migrate the data from the existing one to the new one. -::: - -You can add a prefix to all table names that are managed by SuperTokens. This way, all of them will be renamed in a way that have no clashes with your tables. - -For example, two tables created by SuperTokens are called `emailpassword_users` and `thirdparty_users`. If you add a prefix to them (something like `"my_prefix"`), then the tables will be renamed to `my_prefix_emailpassword_users` and `my_prefix_thirdparty_users`. - -## For MySQL - - - - -```bash -docker run \ - -p 3567:3567 \ - // highlight-next-line - -e MYSQL_TABLE_NAMES_PREFIX="my_prefix" \ - -d registry.supertokens.io/supertokens/supertokens-mysql -``` - - - - -```yaml -# You need to add the following to the config.yaml file. -# The file path can be found by running the "supertokens --help" command - -mysql_table_names_prefix: "my_prefix" -``` - - - -## For PostgreSQL - - - - -```bash -docker run \ - -p 3567:3567 \ - // highlight-next-line - -e POSTGRESQL_TABLE_NAMES_PREFIX="my_prefix" \ - -d registry.supertokens.io/supertokens/supertokens-postgresql -``` - - - - -```yaml -# You need to add the following to the config.yaml file. -# The file path can be found by running the "supertokens --help" command - -postgresql_table_names_prefix: "my_prefix" -``` - - - -## For MongoDB - - - - -```bash -docker run \ - -p 3567:3567 \ - // highlight-next-line - -e MONGODB_COLLECTION_NAMES_PREFIX="my_prefix" \ - -d registry.supertokens.io/supertokens/supertokens-mongodb -``` - - - - - -```yaml -# You need to add the following to the config.yaml file. -# The file path can be found by running the "supertokens --help" command - -mongodb_collection_names_prefix: "my_prefix" -``` - - - diff --git a/v2/emailpassword/custom-ui/init/frontend.mdx b/v2/emailpassword/custom-ui/init/frontend.mdx index 17b237348..bac6ce88b 100644 --- a/v2/emailpassword/custom-ui/init/frontend.mdx +++ b/v2/emailpassword/custom-ui/init/frontend.mdx @@ -4,316 +4,8 @@ title: "Step 1: Frontend" hide_title: true --- +import Redirector from '/src/components/Redirector'; -import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" -import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" -import TabItem from '@theme/TabItem'; -import {Question, Answer}from "/src/components/question" -import AppInfoForm from "/src/components/appInfoForm" -import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" -import WebJsInjector from "/src/components/webJsInjector" + -# Frontend Integration - -## Supported frameworks - -import TechStackSupport from "../../../community/reusableMD/supported-tech-stacks-frontend-custom-ui.mdx" - - - -## 1) Install - - - - - - - -```bash -npm i -s supertokens-web-js -``` - - - - -You need to add all of the following scripts to your app - - - -```bash - - - - -``` - - - - - - - - - - -:::info - -If you want to implement a common authencation experience for both web and mobile, please look at our [**Unified Login guide**](/docs/unified-login/introduction). - -::: - - - - - -```bash -npm i -s supertokens-react-native -# IMPORTANT: If you already have @react-native-async-storage/async-storage as a dependency, make sure the version is 1.12.1 or higher -npm i -s @react-native-async-storage/async-storage -``` - - - - - -Add to your `settings.gradle`: -```bash -dependencyResolutionManagement { - ... - repositories { - ... - maven { url 'https://jitpack.io' } - } -} -``` - -Add the following to you app level's `build.gradle`: -```bash -implementation 'com.github.supertokens:supertokens-android:X.Y.Z' -``` - -You can find the latest version of the SDK [here](https://github.com/supertokens/supertokens-android/releases) (ignore the `v` prefix in the releases). - - - - - -#### Using Cocoapods - -Add the Cocoapod dependency to your Podfile - -```bash -pod 'SuperTokensIOS' -``` - -#### Using Swift Package Manager - -Follow the [official documentation](https://developer.apple.com/documentation/xcode/adding-package-dependencies-to-your-app) to learn how to use Swift Package Manager to add dependencies to your project. - -When adding the dependency use the `master` branch after you enter the supertokens-ios repository URL: - -```bash -https://github.com/supertokens/supertokens-ios -``` - - - - - -Add the dependency to your pubspec.yaml - -```bash -supertokens_flutter: ^X.Y.Z -``` - -You can find the latest version of the SDK [here](https://github.com/supertokens/supertokens-flutter/releases) (ignore the `v` prefix in the releases). - - - - - - - - - -## 2) Call the `init` function - - - - - - - - - -Call the following `init` function at the start of your app (ideally on the global scope). - -```tsx -import SuperTokens from 'supertokens-web-js'; -import Session from 'supertokens-web-js/recipe/session'; -import ^{recipeNameCapitalLetters} from 'supertokens-web-js/recipe/^{codeImportRecipeName}' - -SuperTokens.init({ - appInfo: { - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}", - appName: "...", - }, - recipeList: [ - Session.init(), - ^{recipeNameCapitalLetters}.init(), - ], -}); -``` - - - - - - - - -Call the following `init` function at the start of your app (ideally on the global scope). - -```tsx -import supertokens from "supertokens-web-js-script"; -import supertokensSession from "supertokens-web-js-script/recipe/session"; -import supertokens^{recipeNameCapitalLetters} from 'supertokens-web-js-script/recipe/^{codeImportRecipeName}' -supertokens.init({ - appInfo: { - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}", - appName: "...", - }, - recipeList: [ - supertokensSession.init(), - supertokens^{recipeNameCapitalLetters}.init(), - ], -}); -``` - - - - - - - - - - - - - - - - -Call the following `init` function at the start of your app (ideally on the global scope). - -```tsx -import SuperTokens from 'supertokens-react-native'; - -SuperTokens.init({ - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}", -}); -``` - - - - - - - - - -Add the `SuperTokens.init` function call at the start of your application. - -```kotlin -import android.app.Application -import com.supertokens.session.SuperTokens - -class MainApplication: Application() { - override fun onCreate() { - super.onCreate() - - SuperTokens.Builder(this, "^{form_apiDomain}") - .apiBasePath("^{form_apiBasePath}") - .build() - } -} -``` - - - - - - - - - -Add the `SuperTokens.initialize` function call at the start of your application. - -```swift -import UIKit -import SuperTokensIOS - -fileprivate class ApplicationDelegate: UIResponder, UIApplicationDelegate { - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - do { - try SuperTokens.initialize( - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}" - ) - } catch SuperTokensError.initError(let message) { - // TODO: Handle initialization error - } catch { - // Some other error - } - - return true - } - -} -``` - - - - - - - - - -Add the `SuperTokens.init` function call at the start of your application. - -```dart -import 'package:supertokens_flutter/supertokens.dart'; - -void main() { - SuperTokens.init( - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}", - ); -} -``` - - - - - - - - - - - -## What to do next? -The above code snippet sets up session management network interceptors on the frontend. Our frontend SDK will now be able to automatically save and add session tokens to each request to your API layer and also do auto session refreshing. - -The next steps are to: -- Step 2: setup the backend SDK in your API layer -- Step 3: Setup the SuperTokens core (sign up for managed service, or self host it) -- Step 4: Enable the user management dashboard -- Use the frontend SDK's helper functions to build your own UI - follow along the docs in the "Using your own UI" section and you will find docs for this after "Step 4". diff --git a/v2/emailpassword/custom-ui/init/user-management-dashboard/managing-user-roles-and-permissions.mdx b/v2/emailpassword/custom-ui/init/user-management-dashboard/managing-user-roles-and-permissions.mdx index ec916c7a2..ae431bb4c 100644 --- a/v2/emailpassword/custom-ui/init/user-management-dashboard/managing-user-roles-and-permissions.mdx +++ b/v2/emailpassword/custom-ui/init/user-management-dashboard/managing-user-roles-and-permissions.mdx @@ -4,157 +4,7 @@ title: "Managing user roles and permissions" hide_title: true --- +import Redirector from '/src/components/Redirector'; - - - + -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import TabItem from '@theme/TabItem'; - -# Managing user roles and permissions - -You can manage [user roles and permissions](/docs/userroles/introduction) of your app from the user management dashboard. - -## Initialisation - -To begin configuring user roles and permissions on the user management dashboard, start by initializing the "UserRoles" recipe in the `recipeList` on the backend. - - - - -```tsx -import SuperTokens from "supertokens-node"; -import Dashboard from "supertokens-node/recipe/dashboard"; -import UserRoles from "supertokens-node/recipe/userroles" - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // TODO: Initialise other recipes if needed. - Dashboard.init(), - // highlight-start - UserRoles.init() - // highlight-end - ], -}); -``` -:::important Note - -Please note that the capability to manage roles and permissions from the user management dashboard is available only from Node SDK version `v16.6.0` onwards. - -::: - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/dashboard" - "github.com/supertokens/supertokens-golang/recipe/userroles" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - supertokens.Init(supertokens.TypeInput{ - RecipeList: []supertokens.Recipe{ - // TODO: Initialise other recipes if needed - dashboard.Init(nil), - // highlight-start - userroles.Init(nil), - // highlight-end - }, - }); -} -``` - - - - - -```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import dashboard, userroles - -init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore - recipe_list=[ - # TODO: Initialise other recipes if needed. - dashboard.init(), - # highlight-start - userroles.init() - # highlight-end - ] -) -``` - - - - - - - -## Managing roles and permissions - -When you first use the `UserRoles` recipe, the list of roles will be empty. To create roles, simply click on the "Add Role" button. - -No roles created - -This action will open a modal, enabling you to create a role along with its associated permissions. Permissions are essentially a list of strings assigned to a specific role. - -Create role - -After creating a role, the UI should display a list of all roles in your app. - -Roles list - -You can now preview the role you created by clicking on the role row. The modal provides options to edit or delete the role. - -Preview role - -## Manging roles and users - -To assign a specific role to a user, start by finding the user in the dashboard. Upon clicking the user, navigate to the user details page where you'll find a section for user roles. - -If the selected user is associated with multiple tenants, you can choose a 'tenantId' from the dropdown menu to specify the tenant for which you'd like to assign roles. - -Select tenant - -Click the edit button to start assigning roles. Then, select the "Assign Role" button, and a modal will appear with a list of available roles for assignment to this user. - -Assign role - -To remove a role assigned to a user, simply click on the "X" icon next to that specific role. - -View assigned role - - - - -:::important - -Our Python SDK doesn't currently support roles management via the user management UI. Instead, you can use the functions exposed by the SDK to manage roles and permissions. Check out the guide [here](/docs/userroles/introduction). - -::: - - - - - -:::important - -Our Golang SDK doesn't currently support roles management via the user management UI. Instead, you can use the functions exposed by the SDK to manage roles and permissions. Check out the guide [here](/docs/userroles/introduction). - -::: - - - - - - - diff --git a/v2/emailpassword/custom-ui/init/user-management-dashboard/setup.mdx b/v2/emailpassword/custom-ui/init/user-management-dashboard/setup.mdx index 0c108af83..ce23dfa60 100644 --- a/v2/emailpassword/custom-ui/init/user-management-dashboard/setup.mdx +++ b/v2/emailpassword/custom-ui/init/user-management-dashboard/setup.mdx @@ -4,329 +4,7 @@ title: "Setting up the dashboard" hide_title: true --- -# Setting up the Dashboard +import Redirector from '/src/components/Redirector'; - - - + -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import AppInfoForm from "/src/components/appInfoForm" -import CustomAdmonition from "/src/components/customAdmonition" - - -## Architecture - - - -Flowchart of architecture when using SuperTokens managed service - - -Flowchart of architecture when self-hosting SuperTokens - - - -The Backend SDK serves the user management dashboard which can be accessed on the `/auth/dashboard` path of your API domain. - - -## Initialise the dashboard recipe - - - -To get started, initialise the Dashboard recipe in the `recipeList`. - - - - -```tsx -import SuperTokens from "supertokens-node"; -import Dashboard from "supertokens-node/recipe/dashboard"; - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // TODO: Initialise other recipes - // highlight-start - Dashboard.init(), - // highlight-end - ], -}); -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/dashboard" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - supertokens.Init(supertokens.TypeInput{ - RecipeList: []supertokens.Recipe{ - // TODO: Initialise other recipes - // highlight-start - dashboard.Init(nil), - // highlight-end - }, - }); -} -``` - - - - -```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import dashboard - -init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore - recipe_list=[ - # TODO: Initialise other recipes - # highlight-start - dashboard.init(), - # highlight-end - ] -) -``` - - - - -## Viewing the dashboard - -:::important -The user management dashboard is served by the backend SDK, you have to use your API domain when trying to visit the dashboard. -::: - -Navigate to `^{form_apiDomain}^{form_apiBasePath}/dashboard` to view the dashboard. - -:::note -If you are using Next.js, upon integrating our backend SDK into your Next.js API folder, the dashboard will be accessible by default at `^{form_apiDomain}/api/auth/dashboard`. For frameworks other than Next.js, it can be accessed at `^{form_apiDomain}/auth/dashboard`. Should you have customized the `apiBasePath` configuration property, simply navigate to `^{form_apiDomain}^{form_apiBasePath}/dashboard` to access the dashboard. -::: - -Dashboard login screen UI - -## Creating dashboard credentials - - - -You can create 3 dashboard users* for free. - -If you need to create additional users: - -- For self hosted users, please [sign up](https://supertokens.com/auth) to generate a license key and follow the instructions sent to you by email. -- For managed service users, you can click on the "enable paid features" button on [our dashboard](https://supertokens.com/dashboard-saas), and follow the steps from there on. - -*: A dashboard user is a user that can log into and view the user management dashboard. These users are independent to the users of your application - - - -When you first setup SuperTokens, there are no credentials created for the dashboard. If you click the "Add a new user" button in the dashboard login screen you can see the command you need to execute in order to create credentials. - -Dashboard signup screen UI - -To create credentials you need to make a request to SuperTokens core. - -- The example above uses the demo core `https://try.supertokens.com`, replace this with the connection uri you pass to the backend SDK when initialising SuperTokens. -- Replace `` with your API key. If you are using a self hosted SuperTokens core there is no API key by default. In that case you can either skip or ignore the `api-key` header. -- Replace `` and `` with the appropriate values. - -:::caution -If using self hosted SuperTokens core, you need to make sure that you add an API key to the core in case it's exposed to the internet. Otherwise anyone will be able to create or modify dashboard users. - -You can add an API key to the core by following the instructions "Auth flow customizations" > "SuperTokens core settings" > "Adding API keys" page. -::: - -## Updating dashboard credentials - -You can update the email or password of existing credentials by using the "Forgot Password" button on the dashboard login page. - -Reset your password screen UI - -To update credentials you need to make a request to SuperTokens core. - -- The example above uses the demo core `https://try.supertokens.com`, replace this with the connection uri you pass to the backend SDK when initialising SuperTokens. -- Replace `` with your API key. If you are using a self hosted SuperTokens core there is no API key by default. In that case you can either skip or ignore the `api-key` header. -- Replace `` and `` with the appropriate values. You can use `newEmail` instead of `newPassword` if you want to update the email - - - - - -## Restricting access to dashboard users - -When using the dashboard recipe you can restrict access to certain features by providing a list of emails to be considered as "admins". When a dashboard user logs in with an email not present in this list, they will only be able to perform read operations and all write operations will result in the backend SDKs failing the request. - -You can provide an array of emails to the backend SDK when initialising the dashboard recipe: - -:::important -- Not providing an admins array will result in all dashboard users being allowed both read and write operations -- Providing an empty array as admins will result in all dashboard users having ONLY read access -::: - - - - -```tsx -import SuperTokens from "supertokens-node"; -import Dashboard from "supertokens-node/recipe/dashboard"; - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // TODO: Initialise other recipes - // highlight-start - Dashboard.init({ - admins: [ - "johndoe@gmail.com", - ], - }), - // highlight-end - ], -}); -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/dashboard" - "github.com/supertokens/supertokens-golang/supertokens" - "github.com/supertokens/supertokens-golang/recipe/dashboard/dashboardmodels" -) - -func main() { - supertokens.Init(supertokens.TypeInput{ - RecipeList: []supertokens.Recipe{ - // TODO: Initialise other recipes - // highlight-start - dashboard.Init(&dashboardmodels.TypeInput{ - Admins: &[]string{ - "johndoe@gmail.com", - }, - }), - // highlight-end - }, - }); -} -``` - - - - -```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import dashboard - -init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore - recipe_list=[ - # TODO: Initialise other recipes - # highlight-start - dashboard.init( - admins=[ - "johndoe@gmail.com", - ], - ), - # highlight-end - ] -) -``` - - - - -## Content Security Policy - - - - -If your backend returns a `Content-Security-Policy` header, you will encounter the following UI displaying the CSP violation details. Follow the instructions provided in this UI to make necessary adjustments to your backend CSP configuration. - -![CSP error handled UI](/img/dashboard/csp-error.png) - - -For example, to address the error message displayed in the above screenshot, you need to modify your `original policy`. In the given example, it appears as follows: - -```text -script-src: - 'self' - 'unsafe-inline' - https://google.com - -img-src: - https://google.com -``` - -To resolve this issue, make the following adjustments: - - -```text -script-src: - 'self' - 'unsafe-inline' - https://google.com - -img-src: - https://google.com - https://cdn.jsdelivr.net/gh/supertokens/ - -``` -Essentially, you need to include the domain listed as the `Blocked URI` in your violated directive block within your original policy. - - - -If you return a `Content-Security-Policy` header in from your backend, you will need to include the following directives for the user management dashboard to work correctly - -```text -script-src: - 'self' - 'unsafe-inline' - https://cdn.jsdelivr.net/gh/supertokens/ -img-src: - https://cdn.jsdelivr.net/gh/supertokens/ - https://purecatamphetamine.github.io/ -``` - - - - -If you return a `Content-Security-Policy` header in from your backend, you will need to include the following directives for the user management dashboard to work correctly - -```text -script-src: - 'self' - 'unsafe-inline' - https://cdn.jsdelivr.net/gh/supertokens/ -img-src: - https://cdn.jsdelivr.net/gh/supertokens/ - https://purecatamphetamine.github.io/ -``` - - - - - \ No newline at end of file diff --git a/v2/emailpassword/custom-ui/init/user-management-dashboard/tenant-management/details.mdx b/v2/emailpassword/custom-ui/init/user-management-dashboard/tenant-management/details.mdx index 4719a57cf..1397ce110 100644 --- a/v2/emailpassword/custom-ui/init/user-management-dashboard/tenant-management/details.mdx +++ b/v2/emailpassword/custom-ui/init/user-management-dashboard/tenant-management/details.mdx @@ -4,65 +4,7 @@ title: "Tenant Details" hide_title: true --- - - - +import Redirector from '/src/components/Redirector'; -# Tenant details + -Upon selection or creation of a tenant, you will be presented with the Tenant Details page. The various sections are explained below. - -Tenant details - -## Tenant ID and users - -The first section shows up the tenant ID and the number of users in that tenant. Clicking on `See Users` takes you to the [user management page](/docs/userdashboard/users-listing-and-details) where the users for the selected tenant can be viewed and managed. - -Tenant users - - -## Enabled Login Methods - -This section displays the various login methods available for the tenant. By enabling these toggles, you can make the corresponding login methods accessible to the users within the tenant. - -Appropriate recipes must be enabled to be able to turn on the login methods. For example, - -- to be able to turn on `emailpassword`, EmailPassword recipe must be initialised in the backend. -- to be able to turn on `OTP Phone`, Passwordless recipe must be initialised with flowType `USER_INPUT_CODE` and contactMethod `PHONE` - -:::info - -If you are using our Auth React SDK, make sure to enable [usesDynamicLoginMethods](/docs/passwordless/common-customizations/multi-tenancy/common-domain-login#step-3-tell-supertokens-about-the-saved-tenantid-from-the-previous-step) so that the frontend can automatically show the login methods based on the selection here. - -::: - -Login Methods - -## Secondary factors - -This section displays the various secondary factors available for the tenant. By enabling these toggles, the corresponding factor will be enabled for all users of the tenant. Refer [Multifactor Authentication docs](/docs/mfa/introduction) for more information. - -[MultiFactorAuth](/docs/mfa/backend-setup) recipe must be initialised to be able to enable Secondary Factors. - -Also, appropriate recipes must be initialised in the backend SDK to be able to use a secondary factor. For example, - -- to be able to turn on TOTP, TOTP recipe must be initialised in the backend. -- to be able to turn on `OTP Phone`, Passwordless recipe must be initialised with flowType `USER_INPUT_CODE` and contactMethod `PHONE` - -Secondary Factors - -## Core config - -Core Config - -This section shows the current config values in core for the tenant. You can edit some of these settings by clicking the `pencil` icon next to the property. - -Edit Core Config - -:::caution - -Some of the config values may not be editable since they are being inherited from the App. If using Supertokens managed hosting, they can be modified in the SaaS Dashboard. Else, if you are self-hosting the SuperTokens core, they will have to be edited via Docker env variables or the config.yaml file. - -::: - - diff --git a/v2/emailpassword/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx b/v2/emailpassword/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx index 32c5447bd..6ae231e40 100644 --- a/v2/emailpassword/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx +++ b/v2/emailpassword/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx @@ -4,29 +4,7 @@ title: "Overview" hide_title: true --- - - - +import Redirector from '/src/components/Redirector'; -# Tenant management overview + -You can now manage [tenants, login methods and third party providers](/docs/multitenancy/introduction) and [Multi factor authentication](/docs/mfa/introduction) of your app from the tenant management dashboard. - -Once the dashboard recipe is initialised, the tenant management should be available without requiring any additional steps. - -:::caution - -Currently, this is only available with our Node and Python SDKs. - -::: - -Tenant Management Landing - - -## Creating a new tenant - -Clicking on `Add Tenant` will prompt you to enter the tenant id. Once the tenant id is entered, click on `Create Now` to create the tenant. You will then be taken to the Tenant Details page where you can further manage the newly created tenant. - -Create Tenant - - diff --git a/v2/emailpassword/custom-ui/init/user-management-dashboard/users-listing-and-details.mdx b/v2/emailpassword/custom-ui/init/user-management-dashboard/users-listing-and-details.mdx index ff5945ed2..7be11467c 100644 --- a/v2/emailpassword/custom-ui/init/user-management-dashboard/users-listing-and-details.mdx +++ b/v2/emailpassword/custom-ui/init/user-management-dashboard/users-listing-and-details.mdx @@ -4,38 +4,7 @@ title: "Viewing user list and details" hide_title: true --- - - - +import Redirector from '/src/components/Redirector'; -# Viewing user list and details + -With the user management dashboard you can view the list of users and their details. You can also perform different operations on theses users as mentioned below. - -## Viewing users list - -If you have just created your app, you may not have any users to show on the dashboard. - -Empty dashboard screen UI - -Navigate to the your frontend app and create a user (via the sign up flow). On creation, if you head back to the dashboard and refresh the page, you will see that user: - -One user in dashboard screen UI - -## User details page - -When you select a user you can view detailed information about the user such as email, phone number, user metadata etc. - -User details page screen UI part one - -User details page screen UI part two - -You can edit user information and perform actions such as resetting a user's password or revoke sessions for a user. - -Change password modal UI - -:::important Note -Some features such as user metadata and email verification have to be enabled in your backend before you can use them in the user management dashboard -::: - - diff --git a/v2/emailpassword/custom-ui/multitenant-login.mdx b/v2/emailpassword/custom-ui/multitenant-login.mdx index 5791f7313..bf2091f6c 100644 --- a/v2/emailpassword/custom-ui/multitenant-login.mdx +++ b/v2/emailpassword/custom-ui/multitenant-login.mdx @@ -1,218 +1,11 @@ --- id: multitenant-login -title: "Multitenant login" +title: "Multitenant and Enterprise SSO login" hide_title: true --- -import AppInfoForm from "/src/components/appInfoForm" -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import PythonSyncAsyncSubTabs from "/src/components/tabs/PythonSyncAsyncSubTabs"; -import CoreVersionSubTabs from "/src/components/tabs/CoreVersionSubTabs"; -import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs"; -import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" -import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" -import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" +import Redirector from '/src/components/Redirector'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import MultiTenancyPaidBanner from '../../community/reusableMD/multitenancy/MultiTenancyPaidBanner.mdx' -import CoreInjector from "/src/components/coreInjector" - - - + - - -# Multitenant login - -Multitenant login is a feature that lets you customize the login experience for each of your customers. For example, a customer `customer1` hosted on `customer1.yourdomain.com` can have login with email password (using this recipe), and another customer `customer2` hosted on `customer2.yourdomain.com` can have login with `Active Directory` and `Facebook` (using the thirdparty recipe). - - - - - - - -## Step 1: Create and configure a new tenant in SuperTokens core - -You can create a new tenant using our backend SDKs or via a `cURL` command to the core. - - - - - - - - - - - -```tsx -import Multitenancy from "supertokens-node/recipe/multitenancy"; - -async function createNewTenant() { - - // highlight-start - let resp = await Multitenancy.createOrUpdateTenant("customer1", { - firstFactors: ["emailpassword"] - }); - // highlight-end - - if (resp.createdNew) { - // new tenant was created - } else { - // existing tenant's config was modified. - } -} -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/multitenancy" - "github.com/supertokens/supertokens-golang/recipe/multitenancy/multitenancymodels" -) - -func main() { - tenantId := "customer1" - emailPasswordEnabled := true - - // highlight-start - resp, err := multitenancy.CreateOrUpdateTenant(tenantId, multitenancymodels.TenantConfig{ - EmailPasswordEnabled: &emailPasswordEnabled, - }) - // highlight-end - - if err != nil { - // handle error - } - if resp.OK.CreatedNew { - // new tenant was created - } else { - // existing tenant's config was modified. - } -} -``` - - - - - - - -```python -from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate - -async def some_func(): - tenant_id = "customer1" - result = await create_or_update_tenant(tenant_id, TenantConfigCreateOrUpdate( - first_factors=["emailpassword"] - )) - - if result.status != "OK": - print("handle error") - elif result.created_new: - print("new tenant created") - else: - print("existing tenant's config was modified.") -``` - - - - - -```python -from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate - -tenant_id = "customer1" -result = create_or_update_tenant(tenant_id, TenantConfigCreateOrUpdate( - first_factors=["emailpassword"] -)) - -if result.status != "OK": - print("handle error") -elif result.created_new: - print("new tenant created") -else: - print("existing tenant's config was modified.") -``` - - - - - - - - - - - - - - -```bash -curl --location --request PUT '^{coreInjector_uri_without_quotes}/recipe/multitenancy/tenant/v2' \ ---header 'api-key: ^{coreInjector_api_key_without_quotes}' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "tenantId": "customer1", - "firstFactors": ["emailpassword"] -}' -``` - - - - - -```bash -curl --location --request PUT '^{coreInjector_uri_without_quotes}/recipe/multitenancy/tenant' \ ---header 'api-key: ^{coreInjector_api_key_without_quotes}' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "tenantId": "customer1", - "emailPasswordEnabled": true -}' -``` - - - - - - - - - - - -Create Tenant - - - - - - - - - - - - -## Step 2: Build your multi tenant a UX flow - -The most common multi tenant flows are: -- [Tenants use a common domain to login](../common-customizations/multi-tenancy/common-domain-login): All tenants login using the same page (for example, `example.com/auth`) and are optionally redirected to their sub domain post login. At the start of the login flow, the customer will have to input their tenantId / workspace URL / identifier - as defined by you, and the login methods shown would be based on their tenantId. -- [Tenants use their sub domain to login](../common-customizations/multi-tenancy/sub-domain-login): Here, each tenant has a sub domain assigned to them (for example `customer1.example.com`, `customer2.example.com`, ...), and they would visit their sub domain to login and access their app. Each sub domain's login experience may be different (as defined by you or the tenant). - -SuperTokens is flexible enough to allow other forms of UX as well, but since the above two flow are most common, we provide dedicated docs for them (see the links above). - -## See also - -- Multi tenant [single domain](../common-customizations/multi-tenancy/common-domain-login) or [sub domain](../common-customizations/multi-tenancy/sub-domain-login) login. -- [Tenant data isolation](../common-customizations/multi-tenancy/new-tenant-config#providing-additional-configuration-per-tenant). - - diff --git a/v2/emailpassword/custom-ui/securing-routes.mdx b/v2/emailpassword/custom-ui/securing-routes.mdx index 665ebb50b..adf124a95 100644 --- a/v2/emailpassword/custom-ui/securing-routes.mdx +++ b/v2/emailpassword/custom-ui/securing-routes.mdx @@ -4,590 +4,8 @@ title: "Securing your API and frontend routes" hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -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" -import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" -import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" -import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" + -# Securing your API and frontend routes - -## Protecting APIs - - - - - - - -### Requiring an active session - -For your APIs that require a user to be logged in, use the `verifySession` middleware - - - - - - -```tsx -import express from "express"; -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import { SessionRequest } from "supertokens-node/framework/express"; - -let app = express(); - -// highlight-start -app.post("/like-comment", verifySession(), (req: SessionRequest, res) => { - let userId = req.session!.getUserId(); - // highlight-end - //.... -}); -``` - - - - -```tsx -import Hapi from "@hapi/hapi"; -import { verifySession } from "supertokens-node/recipe/session/framework/hapi"; -import { SessionRequest } from "supertokens-node/framework/hapi"; - -let server = Hapi.server({ port: 8000 }); - -server.route({ - path: "/like-comment", - method: "post", - //highlight-start - options: { - pre: [ - { - method: verifySession() - }, - ], - }, - handler: async (req: SessionRequest, res) => { - let userId = req.session!.getUserId(); - //highlight-end - //... - } -}) -``` - - - -```tsx -import Fastify from "fastify"; -import { verifySession } from "supertokens-node/recipe/session/framework/fastify"; -import { SessionRequest } from "supertokens-node/framework/fastify"; - -let fastify = Fastify(); - -//highlight-start -fastify.post("/like-comment", { - preHandler: verifySession(), -}, (req: SessionRequest, res) => { - let userId = req.session!.getUserId(); - //highlight-end - //.... -}); -``` - - - - -```tsx -import { verifySession } from "supertokens-node/recipe/session/framework/awsLambda"; -import { SessionEventV2 } from "supertokens-node/framework/awsLambda"; - -async function likeComment(awsEvent: SessionEventV2) { - let userId = awsEvent.session!.getUserId(); - //.... -}; - -//highlight-next-line -exports.handler = verifySession(likeComment); -``` - - - - -```tsx -import KoaRouter from "koa-router"; -import { verifySession } from "supertokens-node/recipe/session/framework/koa"; -import { SessionContext } from "supertokens-node/framework/koa"; - -let router = new KoaRouter(); - -//highlight-start -router.post("/like-comment", verifySession(), (ctx: SessionContext, next) => { - let userId = ctx.session!.getUserId(); - //highlight-end - //.... -}); -``` - - - - -```tsx -import { inject, intercept } from "@loopback/core"; -import { RestBindings, MiddlewareContext, post, response } from "@loopback/rest"; -import { verifySession } from "supertokens-node/recipe/session/framework/loopback"; -import { SessionContext } from "supertokens-node/framework/loopback"; - -class LikeComment { - //highlight-start - constructor(@inject(RestBindings.Http.CONTEXT) private ctx: MiddlewareContext) { } - @post("/like-comment") - @intercept(verifySession()) - @response(200) - handler() { - let userId = (this.ctx as SessionContext).session!.getUserId(); - //highlight-end - //.... - } -} -``` - - - - -```tsx -import { superTokensNextWrapper } from 'supertokens-node/nextjs' -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import { SessionRequest } from "supertokens-node/framework/express"; - -// highlight-start -export default async function likeComment(req: SessionRequest, res: any) { - await superTokensNextWrapper( - async (next) => { - await verifySession()(req, res, next); - }, - req, - res - ) - - let userId = req.session!.getUserId(); - // highlight-end - //.... -} -``` - - - - -```tsx -import { NextResponse, NextRequest } from "next/server"; -import SuperTokens from "supertokens-node"; -import { withSession } from "supertokens-node/nextjs"; -// @ts-ignore -import { backendConfig } from "@/app/config/backend"; - -SuperTokens.init(backendConfig()); - -export function POST(request: NextRequest) { - return withSession(request, async (err, session) => { - if (err) { - return NextResponse.json(err, { status: 500 }); - } - const userId = session!.getUserId(); - //.... - return NextResponse.json({}); - }); -} -``` - - - - -```tsx -import { Controller, Post, UseGuards, Session } from "@nestjs/common"; -import { SessionContainer } from "supertokens-node/recipe/session"; -// @ts-ignore -import { AuthGuard } from './auth/auth.guard'; - -@Controller() -export class ExampleController { - @Post('example') - @UseGuards(new AuthGuard()) // For more information about this guard please read our NestJS guide. - async postExample(@Session() session: SessionContainer): Promise { - //highlight-start - let userId = session.getUserId(); - - //highlight-end - //.... - return true; - } -} -``` - - - - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/supertokens/supertokens-golang/recipe/session" -) - -func main() { - _ = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - // Wrap the API handler in session.VerifySession - session.VerifySession(nil, likeCommentAPI).ServeHTTP(rw, r) - }) -} - -func likeCommentAPI(w http.ResponseWriter, r *http.Request) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(r.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/gin-gonic/gin" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" -) - -func main() { - router := gin.New() - - // Wrap the API handler in session.VerifySession - router.POST("/likecomment", verifySession(nil), likeCommentAPI) -} - -// This is a function that wraps the supertokens verification function -// to work the gin -func verifySession(options *sessmodels.VerifySessionOptions) gin.HandlerFunc { - return func(c *gin.Context) { - session.VerifySession(options, func(rw http.ResponseWriter, r *http.Request) { - c.Request = c.Request.WithContext(r.Context()) - c.Next() - })(c.Writer, c.Request) - // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly - c.Abort() - } -} - -func likeCommentAPI(c *gin.Context) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(c.Request.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/go-chi/chi" - "github.com/supertokens/supertokens-golang/recipe/session" -) - -func main() { - r := chi.NewRouter() - - // Wrap the API handler in session.VerifySession - r.Post("/likecomment", session.VerifySession(nil, likeCommentAPI)) -} - -func likeCommentAPI(w http.ResponseWriter, r *http.Request) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(r.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/gorilla/mux" - "github.com/supertokens/supertokens-golang/recipe/session" -) - -func main() { - router := mux.NewRouter() - - // Wrap the API handler in session.VerifySession - router.HandleFunc("/likecomment", session.VerifySession(nil, likeCommentAPI)).Methods(http.MethodPost) -} - -func likeCommentAPI(w http.ResponseWriter, r *http.Request) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(r.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - - - - - - -```python -from supertokens_python.recipe.session.framework.fastapi import verify_session -from supertokens_python.recipe.session import SessionContainer -from fastapi import Depends - -# highlight-start -@app.post('/like_comment') # type: ignore -async def like_comment(session: SessionContainer = Depends(verify_session())): - user_id = session.get_user_id() - # highlight-end - - print(user_id) -``` - - - - -```python -from supertokens_python.recipe.session.framework.flask import verify_session -from supertokens_python.recipe.session import SessionContainer -from flask import g - -# highlight-start -@app.route('/update-jwt', methods=['POST']) # type: ignore -@verify_session() -def like_comment(): - session: SessionContainer = g.supertokens # type: ignore - - user_id = session.get_user_id() - # highlight-end - - print(user_id) -``` - - - - -```python -from supertokens_python.recipe.session.framework.django.asyncio import verify_session -from django.http import HttpRequest -from supertokens_python.recipe.session import SessionContainer - -# highlight-start -@verify_session() -async def like_comment(request: HttpRequest): - session: SessionContainer = request.supertokens # type: ignore - - user_id = session.get_user_id() - # highlight-end - - print(user_id) -``` - - - - - - - -The `verifySession` function returns a `401` to the frontend if a session doesn't exist, or if the access token has expired, in which case, our frontend SDK automatically refreshes the session. - -In case of successful session verification, you get access to a `session` object using which you can get the user's ID, or manipulate the session information. - - - - - - - -### Microservice authentication - -For authentication between microservices on your backend, checkout the [microservice auth guide](/docs/microservice_auth/client-credentials). - - - -## Protecting frontend routes - - - - - - - - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```tsx -import Session from 'supertokens-web-js/recipe/session'; - -async function doesSessionExist() { - if (await Session.doesSessionExist()) { - // user is logged in - } else { - // user has not logged in yet - } -} -``` - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```tsx -import Session from 'supertokens-web-js-script/recipe/session'; -async function doesSessionExist() { - if (await Session.doesSessionExist()) { - // user is logged in - } else { - // user has not logged in yet - } -} -``` - - - - - - - - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```tsx -import SuperTokens from 'supertokens-react-native'; - -async function doesSessionExist() { - if (await SuperTokens.doesSessionExist()) { - // user is logged in - } else { - // user has not logged in yet - } -} -``` - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```kotlin -import android.app.Application -import com.supertokens.session.SuperTokens - -class MainApplication: Application() { - fun doesSessionExist() { - if (SuperTokens.doesSessionExist(this.applicationContext)) { - // user is logged in - } else { - // user has not logged in yet - } - } -} -``` - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```swift -import UIKit -import SuperTokensIOS - -fileprivate class ViewController: UIViewController { - func doesSessionExist() { - if SuperTokens.doesSessionExist() { - // User is logged in - } else { - // User is not logged in - } - } -} -``` - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```dart -import 'package:supertokens_flutter/supertokens.dart'; - -Future doesSessionExist() async { - return await SuperTokens.doesSessionExist(); -} -``` - - - - - - - - - - - - - - - -## See also - -- Optional sessions [for APIs](../common-customizations/sessions/session-verification-in-api/verify-session#optional-session-verification) and [the frontend](../common-customizations/sessions/checking-session-front-end) -- [Verifying session without using a middleware](../common-customizations/sessions/session-verification-in-api/get-session) -- Session claim validation [for APIs](../common-customizations/sessions/claims/claim-validators) and [the frontend](../common-customizations/sessions/claims/claim-validators) -- [Changing session lifetime](../common-customizations/sessions/change-session-timeout) -- [Sharing session across sub domains](../common-customizations/sessions/share-sessions-across-sub-domains) - - diff --git a/v2/emailpassword/custom-ui/sign-out.mdx b/v2/emailpassword/custom-ui/sign-out.mdx index af9169fa0..81b1a86e9 100644 --- a/v2/emailpassword/custom-ui/sign-out.mdx +++ b/v2/emailpassword/custom-ui/sign-out.mdx @@ -4,135 +4,9 @@ title: Implementing sign out hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" -import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" -import TabItem from '@theme/TabItem'; -import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" -# Implementing sign out + -The `signOut` method revokes the session on the frontend and on the backend. Calling this function without a valid session also yields a successful response. - - - - - - -```tsx -import Session from "supertokens-web-js/recipe/session"; - -async function logout () { - // highlight-next-line - await Session.signOut(); - window.location.href = "/auth"; // or to wherever your logic page is -} -``` - - - - -```tsx -import supertokensSession from "supertokens-web-js-script/recipe/session"; -async function logout () { - // highlight-next-line - await supertokensSession.signOut(); - window.location.href = "/auth"; // or to wherever your logic page is -} -``` - - - - - - - - - - - - -```tsx -import SuperTokens from "supertokens-react-native"; - -async function logout () { - // highlight-next-line - await SuperTokens.signOut(); - // navigate to the login screen.. -} -``` - - - - - -```kotlin -import android.app.Application -import com.supertokens.session.SuperTokens - -class MainApplication: Application() { - fun logout() { - // highlight-next-line - SuperTokens.signOut(this); - // navigate to the login screen.. - } -} -``` - - - - - -```swift -import UIKit -import SuperTokensIOS - -fileprivate class ViewController: UIViewController { - func signOut() { - SuperTokens.signOut(completionHandler: { - error in - - if error != nil { - // handle error - } else { - // Signed out successfully - } - }) - } -} -``` - - - - - -```dart -import 'package:supertokens_flutter/supertokens.dart'; - -Future signOut() async { - await SuperTokens.signOut( - completionHandler: (error) { - // handle error if any - } - ); -} -``` - - - - - - - - - -- On success, the `signOut` function does not redirect the user to another page, so you must redirect the user yourself. -- The `signOut` function calls the signout API exposed by the session recipe on the backend. -- If you call the `signOut` function whilst the access token has expired, but the refresh token still exists, our SDKs will do an automatic session refresh before revoking the session. - -## See also - -- [Revoking a session on the backend](../common-customizations/sessions/revoke-session) diff --git a/v2/emailpassword/introduction.mdx b/v2/emailpassword/introduction.mdx index 40575145c..169fb5899 100644 --- a/v2/emailpassword/introduction.mdx +++ b/v2/emailpassword/introduction.mdx @@ -1,46 +1,14 @@ --- id: introduction -title: Introduction & Architecture +title: Introduction hide_title: true +hide_table_of_contents: true --- -# EmailPassword login +import Redirector from '/src/components/Redirector'; -## Features -- Sign-up / Sign-in with email ID and password
-- Forgot password flow using email
-- Secure session management
-- Email verification
+ - - - -## Demo application -- See our [live demo app](https://^{docsLinkRecipeName}.demo.supertokens.com/). -- We have a read-only user management dashboard (with fake data), and it can be accessed [on this link](https://dashboard.demo.supertokens.com/api/auth/dashboard). The credentials for logging in are: - ```text - email: demo@supertokens.com - password: abcd1234 - ``` -- Generate a starter app - ```bash - npx create-supertokens-app@latest --recipe=^{docsLinkRecipeName} - ``` -- The user management dashboard for the starter app can be accessed on `http://localhost:3001/auth/dashboard`. You will have to first create users (instrs are on the dashboard UI), before logging in. - -## Architecture - -import Architecture from "../community/reusableMD/intro-architecture.mdx" - - - -## Next steps - -import NextSteps from "../community/reusableMD/intro-next-steps.mdx" - - - - \ No newline at end of file diff --git a/v2/emailpassword/pre-built-ui/enable-email-verification.mdx b/v2/emailpassword/pre-built-ui/enable-email-verification.mdx index 401e208f8..4f3ade3f4 100644 --- a/v2/emailpassword/pre-built-ui/enable-email-verification.mdx +++ b/v2/emailpassword/pre-built-ui/enable-email-verification.mdx @@ -4,810 +4,8 @@ title: Enable email verification hide_title: true --- -# Enable email verification +import Redirector from '/src/components/Redirector'; - - - -import CustomAdmonition from "/src/components/customAdmonition" -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"; + - -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 - - - - -```tsx -import SuperTokens from "supertokens-node"; -import EmailVerification from "supertokens-node/recipe/emailverification"; -import Session from "supertokens-node/recipe/session"; - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // highlight-start - EmailVerification.init({ - mode: "REQUIRED", // or "OPTIONAL" - }), - // highlight-end - Session.init(), - ], -}); -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/emailverification" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evmodels" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - supertokens.Init(supertokens.TypeInput{ - RecipeList: []supertokens.Recipe{ - // highlight-start - emailverification.Init(evmodels.TypeInput{ - Mode: evmodels.ModeRequired, // or evmodels.ModeOptional - }), - // highlight-end - session.Init(&sessmodels.TypeInput{}), - }, - }) -} -``` - - - - -```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import session -from supertokens_python.recipe import emailverification - -init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore - recipe_list=[ - # highlight-start - emailverification.init(mode='REQUIRED'), # or 'OPTIONAL' - # highlight-end - session.init() - ] -) -``` - - - - - - - - - - -import {Question}from "/src/components/question" - -## Step 2: Frontend setup - - - - - - - -```tsx -import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; -import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui"; -import EmailVerification from "supertokens-auth-react/recipe/emailverification"; -^{prebuiltuiimport} -import { EmailVerificationPreBuiltUI } from "supertokens-auth-react/recipe/emailverification/prebuiltui"; -import Session from "supertokens-auth-react/recipe/session"; -import reactRouterDOM, { Routes, BrowserRouter as Router, Route } from "react-router-dom"; - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // highlight-start - EmailVerification.init({ - mode: "REQUIRED", // or "OPTIONAL" - }), - // highlight-end - Session.init(), - ], -}); - -function App() { - return ( - -
- -
- - // highlight-start - {getSuperTokensRoutesForReactRouterDom(reactRouterDOM, [^{recipePreBuiltUINameCapitalLetters}, EmailVerificationPreBuiltUI])} - // highlight-end - // ... other routes - -
-
-
-
- ); -} -``` - -
- - - -```tsx -import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; -import { canHandleRoute, getRoutingComponent } from "supertokens-auth-react/ui"; -import EmailVerification from "supertokens-auth-react/recipe/emailverification"; -^{prebuiltuiimport} -import { EmailVerificationPreBuiltUI } from "supertokens-auth-react/recipe/emailverification/prebuiltui"; -import Session from "supertokens-auth-react/recipe/session"; - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // highlight-start - EmailVerification.init({ - mode: "REQUIRED", // or "OPTIONAL" - }), - // highlight-end - Session.init(), - ], -}); - -function App() { - // highlight-start - if (canHandleRoute([^{recipePreBuiltUINameCapitalLetters}, EmailVerificationPreBuiltUI])) { - return getRoutingComponent([^{recipePreBuiltUINameCapitalLetters}, EmailVerificationPreBuiltUI]) - } - // highlight-end - return ( - {/*Your app*/} - ); -} -``` - - - -
- -
- - - -```tsx -// this goes in the auth route config of your frontend app (once the pre built UI script has been loaded) -import {init as supertokensUIInit} from "supertokens-auth-react-script"; -import supertokensUIEmailVerification from "supertokens-auth-react-script/recipe/emailverification"; -import supertokensUISession from "supertokens-auth-react-script/recipe/session"; - -supertokensUIInit({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // highlight-start - supertokensUIEmailVerification.init({ - mode: "REQUIRED", // or "OPTIONAL" - }), - // highlight-end - supertokensUISession.init(), - ], -}); -``` - - - -
- - - - - - - -## Step 3: Checking if the user's email is verified in your APIs - -

If using REQUIRED mode

- -On the backend, when you initialize the email verification recipe in this mode, the `verifySession` middleware automatically checks if the user's email is verified based on the contents of the session's payload. If the email is not verified, the `verifySession` middleware will return a `403` status code to the client. - -

If using OPTIONAL mode

- -In this mode, you need to check if the email is verified yourself in the APIs in which you want this constraint. The verification status should already be in the session's payload. - - - - - - -```tsx -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import express from "express"; -import { SessionRequest } from "supertokens-node/framework/express"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let app = express(); - -app.post( - "/update-blog", - verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - }), - async (req: SessionRequest, res) => { - // All validator checks have passed and the user has a verified email address - } -); -``` - - - -```tsx -import Hapi from "@hapi/hapi"; -import { verifySession } from "supertokens-node/recipe/session/framework/hapi"; -import {SessionRequest} from "supertokens-node/framework/hapi"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let server = Hapi.server({ port: 8000 }); - -server.route({ - path: "/update-blog", - method: "post", - options: { - pre: [ - { - method: verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - }), - }, - ], - }, - handler: async (req: SessionRequest, res) => { - // All validator checks have passed and the user has a verified email address - } -}) -``` - - - -```tsx -import Fastify from "fastify"; -import { verifySession } from "supertokens-node/recipe/session/framework/fastify"; -import { SessionRequest } from "supertokens-node/framework/fastify"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let fastify = Fastify(); - -fastify.post("/update-blog", { - preHandler: verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - }), -}, async (req: SessionRequest, res) => { - // All validator checks have passed and the user has a verified email address -}); -``` - - - - -```tsx -import { verifySession } from "supertokens-node/recipe/session/framework/awsLambda"; -import { SessionEvent } from "supertokens-node/framework/awsLambda"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -async function updateBlog(awsEvent: SessionEvent) { - // All validator checks have passed and the user has a verified email address -}; - -exports.handler = verifySession(updateBlog, { - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] -}); -``` - - - - -```tsx -import KoaRouter from "koa-router"; -import { verifySession } from "supertokens-node/recipe/session/framework/koa"; -import {SessionContext} from "supertokens-node/framework/koa"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let router = new KoaRouter(); - -router.post("/update-blog", verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - }), async (ctx: SessionContext, next) => { - // All validator checks have passed and the user has a verified email address -}); -``` - - - - -```tsx -import { inject, intercept } from "@loopback/core"; -import { RestBindings, MiddlewareContext, post, response } from "@loopback/rest"; -import { verifySession } from "supertokens-node/recipe/session/framework/loopback"; -import Session from "supertokens-node/recipe/session"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -class SetRole { - constructor(@inject(RestBindings.Http.CONTEXT) private ctx: MiddlewareContext) { } - @post("/update-blog") - @intercept(verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - })) - @response(200) - async handler() { - // All validator checks have passed and the user has a verified email address - } -} -``` - - - - -```tsx -import { superTokensNextWrapper } from 'supertokens-node/nextjs' -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import { SessionRequest } from "supertokens-node/framework/express"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -// highlight-start -export default async function setRole(req: SessionRequest, res: any) { - await superTokensNextWrapper( - async (next) => { - await verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - })(req, res, next); - }, - req, - res - ) - // All validator checks have passed and the user has a verified email address -} -``` - - - - -```tsx -import SuperTokens from "supertokens-node"; -import { NextResponse, NextRequest } from "next/server"; -import { withSession } from "supertokens-node/nextjs"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; -// @ts-ignore -import { backendConfig } from "@/app/config/backend"; - -SuperTokens.init(backendConfig()); - -export async function POST(request: NextRequest) { - return withSession(request, async (err, session) => { - if (err) { - return NextResponse.json(err, { status: 500 }); - } - // All validator checks have passed and the user has a verified email address - return NextResponse.json({ message: "Your email is verified!" }); - }, - { - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - } - ); -} -``` - - - - -```tsx -import { Controller, Post, UseGuards, Request, Response, Session } from "@nestjs/common"; -import { SessionContainer, SessionClaimValidator } from "supertokens-node/recipe/session"; -// @ts-ignore -import { AuthGuard } from './auth/auth.guard'; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -@Controller() -export class ExampleController { - @Post('example') - @UseGuards(new AuthGuard({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators: SessionClaimValidator[]) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - })) - async postExample(@Session() session: SessionContainer): Promise { - // All validator checks have passed and the user has a verified email address - return true; - } -} -``` - - - - - - - - -```go -import ( - "net/http" - - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - _ = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - session.VerifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }, exampleAPI).ServeHTTP(rw, r) - }) -} - -func exampleAPI(w http.ResponseWriter, r *http.Request) { - // TODO: session is verified and all validators have passed.. -} -``` - - - - -```go -import ( - "net/http" - - "github.com/gin-gonic/gin" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - router := gin.New() - - // Wrap the API handler in session.VerifySession - router.POST("/likecomment", verifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }), exampleAPI) -} - -// This is a function that wraps the supertokens verification function -// to work the gin -func verifySession(options *sessmodels.VerifySessionOptions) gin.HandlerFunc { - return func(c *gin.Context) { - session.VerifySession(options, func(rw http.ResponseWriter, r *http.Request) { - c.Request = c.Request.WithContext(r.Context()) - c.Next() - })(c.Writer, c.Request) - // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly - c.Abort() - } -} - -func exampleAPI(c *gin.Context) { - // TODO: session is verified and all claim validators pass. -} -``` - - - - -```go -import ( - "net/http" - - "github.com/go-chi/chi" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - r := chi.NewRouter() - - // Wrap the API handler in session.VerifySession - r.Post("/likecomment", session.VerifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }, exampleAPI)) -} - -func exampleAPI(w http.ResponseWriter, r *http.Request) { - // TODO: session is verified and all claim validators pass. -} -``` - - - - -```go -import ( - "net/http" - - "github.com/gorilla/mux" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - router := mux.NewRouter() - - // Wrap the API handler in session.VerifySession - router.HandleFunc("/likecomment", session.VerifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }, exampleAPI)).Methods(http.MethodPost) -} - -func exampleAPI(w http.ResponseWriter, r *http.Request) { - // TODO: session is verified and all claim validators pass. -} -``` - - - - - - - - - -```python -from supertokens_python.recipe.session.framework.fastapi import verify_session -from supertokens_python.recipe.emailverification import EmailVerificationClaim -from supertokens_python.recipe.session import SessionContainer -from fastapi import Depends - -@app.post('/like_comment') # type: ignore -async def like_comment(session: SessionContainer = Depends( - verify_session( - # highlight-start - # We add the EmailVerificationClaim's is_verified validator - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \ - [EmailVerificationClaim.validators.is_verified()] - # highlight-end - ) -)): - # All validator checks have passed and the user has a verified email address - pass -``` - - - - -```python -from supertokens_python.recipe.session.framework.flask import verify_session -from supertokens_python.recipe.emailverification import EmailVerificationClaim - -@app.route('/update-jwt', methods=['POST']) # type: ignore -@verify_session( - # highlight-start - # We add the EmailVerificationClaim's is_verified validator - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \ - [EmailVerificationClaim.validators.is_verified()] - # highlight-end -) -def like_comment(): - # All validator checks have passed and the user has a verified email address - pass -``` - - - - -```python -from supertokens_python.recipe.session.framework.django.asyncio import verify_session -from django.http import HttpRequest -from supertokens_python.recipe.emailverification import EmailVerificationClaim - -@verify_session( - # highlight-start - # We add the EmailVerificationClaim's is_verified validator - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \ - [EmailVerificationClaim.validators.is_verified()] - # highlight-end -) -async def like_comment(request: HttpRequest): - # All validator checks have passed and the user has a verified email address - pass -``` - - - - - - - -We add the SDK's `EmailVerificationClaim` validator to the `verifySession` middleware call as shown above, and that will only allow access if the email is verified, else it will return `403` to the frontend. - - - - - - - -## Step 4: Protecting frontend routes - - - - -

If using REQUIRED mode

- -Wrapping your website routes using `` should enforce email verification. If the user's email is not verified, SuperTokens will automatically redirect the user to the email verification screen. - -

If using OPTIONAL mode

- -```tsx -import React from "react"; -import { SessionAuth, useSessionContext } from 'supertokens-auth-react/recipe/session'; -import { EmailVerificationClaim } from 'supertokens-auth-react/recipe/emailverification'; - -const VerifiedRoute = (props: React.PropsWithChildren) => { - return ( - - - {props.children} - - - ); -} - -function InvalidClaimHandler(props: React.PropsWithChildren) { - let sessionContext = useSessionContext(); - if (sessionContext.loading) { - return null; - } - - if (sessionContext.invalidClaims.some(i => i.id === EmailVerificationClaim.id)) { - // Alternatively you could redirect the user to the email verification screen to trigger the verification email - // Note: /auth/verify-email is the default email verification path - // window.location.assign("/auth/verify-email") - return
You cannot access this page because your email address is not verified.
- } - - // We show the protected route since all claims validators have - // passed implying that the user has verified their email. - return
{props.children}
; -} -``` -Above we are creating a generic component called `VerifiedRoute` which enforces that its child components can only be rendered if the user has a verified email address. - -In the `VerifiedRoute` component, we use the `SessionAuth` wrapper to ensure that the session exists. The `SessionAuth` wrapper will create a context that contains a prop called `invalidClaims` which will contain a list of all claim validations that have failed. - -The email verification recipe on the frontend, adds the `EmailVerificationClaim` validator automatically, so if the user's email is not verified, the `invalidClaims` prop will contain information about that. Alternatively you could also redirect the user to the default email verification path to trigger the sending of the verification email. - -We check the result of the validation in the `InvalidClaimHandler` component which displays `"You cannot access this page because your email address is not verified."` if the `EmailVerificationClaim` validator failed. - -If all validations pass, we render the `props.children` component. - -
- - - -```tsx -import Session from "supertokens-web-js/recipe/session"; -import { EmailVerificationClaim } from "supertokens-web-js/recipe/emailverification"; - -async function shouldLoadRoute(): Promise { - if (await Session.doesSessionExist()) { - // highlight-start - let validationErrors = await Session.validateClaims(); - - if (validationErrors.length === 0) { - // user has verified their email address - return true; - } else { - for (const err of validationErrors) { - if (err.id === EmailVerificationClaim.id) { - // email is not verified - } - } - } - // highlight-end - } - // a session does not exist, or email is not verified - return false -} -``` - -In your protected routes, you need to first check if a session exists, and then call the `Session.validateClaims` function as shown above. This function inspects the session's contents and runs claim validators on them. If a claim validator fails, it will be reflected in the `validationErrors` variable. The `EmailVerificationClaim` validator will be automatically checked by this function since you have initialized the email verification recipe. - - - -
- - - - - - - -## See also - -- [Post email verification action](../common-customizations/email-verification/handling-email-verification-success) -- [Change email verification link's lifetime](../common-customizations/email-verification/changing-token-lifetime) -- [Customise email template or email delivery method](../email-delivery/about) -- [Manually changing email verification status for a user](../common-customizations/email-verification/changing-email-verification-status) -- [Generating email verification links manually](../common-customizations/email-verification/generate-link-manually) -- [Replacing, customising or embedding the frontend UI](../common-customizations/email-verification/embed-in-page) -- 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/emailpassword/pre-built-ui/handling-session-tokens.mdx b/v2/emailpassword/pre-built-ui/handling-session-tokens.mdx index 69b80421f..6ff934e75 100644 --- a/v2/emailpassword/pre-built-ui/handling-session-tokens.mdx +++ b/v2/emailpassword/pre-built-ui/handling-session-tokens.mdx @@ -4,165 +4,7 @@ title: Handling session tokens hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -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'; + -# Handling session tokens - - - - - -:::success -No action required. -::: - -Our frontend SDK handles everything for you. You only need to make sure that you have called `supertokens.init` before making any network requests. - -Our SDK adds interceptors to `fetch` and `XHR` (used by `axios`) to save and add session tokens from and to the request. - -By default, our web SDKs use cookies to provide credentials. - - - - - -## Getting the access token - -:::caution -Our SDK automatically handles adding the access token to request headers. You only need to add the access token to the request if you want to send the access token to a different API domain than what is configured on the frontend SDK init function call. -::: - -If you are using a header-based session or enabled `exposeAccessTokenToFrontendInCookieBasedAuth` (see below), you can read the access token on the frontend using the `getAccessToken` method: - - - - - - - - -```tsx -import Session from "supertokens-auth-react/recipe/session"; - -async function getToken(): Promise { - // highlight-next-line - const accessToken = await Session.getAccessToken(); - console.log(accessToken); -} -``` - - - - - -```tsx -import Session from "supertokens-web-js/recipe/session"; - -async function getToken(): Promise { - // highlight-next-line - const accessToken = await Session.getAccessToken(); - console.log(accessToken); -} -``` - - - - - - - -### If using cookie-based sessions - -:::caution -This will expose the access token to the frontend, meaning it could be vulnerable to XSS attacks. -::: - -:::important -If you are using header-based sessions, you can skip this step -::: - -By enabling this setting, you'll expose the access token to your frontend code even if you use cookies for authentication. - - - - - - - - -```tsx -import SuperTokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; - -SuperTokens.init({ - supertokens: { - connectionURI: "..." - }, - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "..." - }, - recipeList: [ - Session.init({ - //highlight-start - exposeAccessTokenToFrontendInCookieBasedAuth: true, - //highlight-end - }) - ] -}); -``` - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - supertokens.Init(supertokens.TypeInput{ - RecipeList: []supertokens.Recipe{ - session.Init(&sessmodels.TypeInput{ - //highlight-start - ExposeAccessTokenToFrontendInCookieBasedAuth: true, - //highlight-end - }), - }, - }) -} -``` - - - -```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import session - -init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore - recipe_list=[ - session.init( - # highlight-next-line - expose_access_token_to_frontend_in_cookie_based_auth=True - ) - ] -) -``` - - - - - diff --git a/v2/emailpassword/pre-built-ui/multitenant-login.mdx b/v2/emailpassword/pre-built-ui/multitenant-login.mdx index e8b6b3896..bf2091f6c 100644 --- a/v2/emailpassword/pre-built-ui/multitenant-login.mdx +++ b/v2/emailpassword/pre-built-ui/multitenant-login.mdx @@ -4,211 +4,8 @@ title: "Multitenant and Enterprise SSO login" hide_title: true --- -import AppInfoForm from "/src/components/appInfoForm" -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import PythonSyncAsyncSubTabs from "/src/components/tabs/PythonSyncAsyncSubTabs"; -import CoreVersionSubTabs from "/src/components/tabs/CoreVersionSubTabs"; -import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs"; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import MultiTenancyPaidBanner from '../../community/reusableMD/multitenancy/MultiTenancyPaidBanner.mdx' -import CoreInjector from "/src/components/coreInjector" -import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" +import Redirector from '/src/components/Redirector'; - - - - + -# Multitenant login - -Multitenant login is a feature that lets you customize the login experience for each of your customers. For example, a customer `customer1` hosted on `customer1.yourdomain.com` can have login with email password (using this recipe), and another customer `customer2` hosted on `customer2.yourdomain.com` can have login with `Active Directory` and `Facebook` (using the thirdparty recipe). - - - - - - - -## Step 1: Create and configure a new tenant in SuperTokens core - -You can create a new tenant using our backend SDKs or via a `cURL` command to the core. - - - - - - - - - - - -```tsx -import Multitenancy from "supertokens-node/recipe/multitenancy"; - -async function createNewTenant() { - - // highlight-start - let resp = await Multitenancy.createOrUpdateTenant("customer1", { - firstFactors: ["emailpassword"] - }); - // highlight-end - - if (resp.createdNew) { - // new tenant was created - } else { - // existing tenant's config was modified. - } -} -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/multitenancy" - "github.com/supertokens/supertokens-golang/recipe/multitenancy/multitenancymodels" -) - -func main() { - tenantId := "customer1" - emailPasswordEnabled := true - - // highlight-start - resp, err := multitenancy.CreateOrUpdateTenant(tenantId, multitenancymodels.TenantConfig{ - EmailPasswordEnabled: &emailPasswordEnabled, - }) - // highlight-end - - if err != nil { - // handle error - } - if resp.OK.CreatedNew { - // new tenant was created - } else { - // existing tenant's config was modified. - } -} -``` - - - - - - - -```python -from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate - -async def some_func(): - tenant_id = "customer1" - result = await create_or_update_tenant(tenant_id, TenantConfigCreateOrUpdate( - first_factors=["emailpassword"] - )) - - if result.status != "OK": - print("handle error") - elif result.created_new: - print("new tenant created") - else: - print("existing tenant's config was modified.") -``` - - - - - -```python -from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate - -tenant_id = "customer1" -result = create_or_update_tenant(tenant_id, TenantConfigCreateOrUpdate( - first_factors=["emailpassword"] -)) - -if result.status != "OK": - print("handle error") -elif result.created_new: - print("new tenant created") -else: - print("existing tenant's config was modified.") -``` - - - - - - - - - - - - - - -```bash -curl --location --request PUT '^{coreInjector_uri_without_quotes}/recipe/multitenancy/tenant/v2' \ ---header 'api-key: ^{coreInjector_api_key_without_quotes}' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "tenantId": "customer1", - "firstFactors": ["emailpassword"] -}' -``` - - - - - -```bash -curl --location --request PUT '^{coreInjector_uri_without_quotes}/recipe/multitenancy/tenant' \ ---header 'api-key: ^{coreInjector_api_key_without_quotes}' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "tenantId": "customer1", - "emailPasswordEnabled": true -}' -``` - - - - - - - - - - - -Create Tenant - - - - - - - - - - - -## Step 2: Build your multi tenant a UX flow - -The most common multi tenant flows are: -- [Tenants use a common domain to login](../common-customizations/multi-tenancy/common-domain-login): All tenants login using the same page (for example, `example.com/auth`) and are optionally redirected to their sub domain post login. At the start of the login flow, the customer will have to input their tenantId / workspace URL / identifier - as defined by you, and the login methods shown would be based on their tenantId. -- [Tenants use their sub domain to login](../common-customizations/multi-tenancy/sub-domain-login): Here, each tenant has a sub domain assigned to them (for example `customer1.example.com`, `customer2.example.com`, ...), and they would visit their sub domain to login and access their app. Each sub domain's login experience may be different (as defined by you or the tenant). - -SuperTokens is flexible enough to allow other forms of UX as well, but since the above two flow are most common, we provide dedicated docs for them (see the links above). - -## See also - -- Multi tenant [single domain](../common-customizations/multi-tenancy/common-domain-login) or [sub domain](../common-customizations/multi-tenancy/sub-domain-login) login. -- [Tenant data isolation](../common-customizations/multi-tenancy/new-tenant-config#providing-additional-configuration-per-tenant). - - diff --git a/v2/emailpassword/pre-built-ui/securing-routes.mdx b/v2/emailpassword/pre-built-ui/securing-routes.mdx index a45ea028c..adf124a95 100644 --- a/v2/emailpassword/pre-built-ui/securing-routes.mdx +++ b/v2/emailpassword/pre-built-ui/securing-routes.mdx @@ -4,530 +4,8 @@ title: "Securing your API and frontend routes" hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -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 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 + -## Protecting APIs - - - - - - - -### Requiring an active session - -For your APIs that require a user to be logged in, use the `verifySession` middleware - - - - - - -```tsx -import express from "express"; -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import { SessionRequest } from "supertokens-node/framework/express"; - -let app = express(); - -// highlight-start -app.post("/like-comment", verifySession(), (req: SessionRequest, res) => { - let userId = req.session!.getUserId(); - // highlight-end - //.... -}); -``` - - - - -```tsx -import Hapi from "@hapi/hapi"; -import { verifySession } from "supertokens-node/recipe/session/framework/hapi"; -import { SessionRequest } from "supertokens-node/framework/hapi"; - -let server = Hapi.server({ port: 8000 }); - -server.route({ - path: "/like-comment", - method: "post", - //highlight-start - options: { - pre: [ - { - method: verifySession() - }, - ], - }, - handler: async (req: SessionRequest, res) => { - let userId = req.session!.getUserId(); - //highlight-end - //... - } -}) -``` - - - -```tsx -import Fastify from "fastify"; -import { verifySession } from "supertokens-node/recipe/session/framework/fastify"; -import { SessionRequest } from "supertokens-node/framework/fastify"; - -let fastify = Fastify(); - -//highlight-start -fastify.post("/like-comment", { - preHandler: verifySession(), -}, (req: SessionRequest, res) => { - let userId = req.session!.getUserId(); - //highlight-end - //.... -}); -``` - - - - -```tsx -import { verifySession } from "supertokens-node/recipe/session/framework/awsLambda"; -import { SessionEventV2 } from "supertokens-node/framework/awsLambda"; - -async function likeComment(awsEvent: SessionEventV2) { - let userId = awsEvent.session!.getUserId(); - //.... -}; - -//highlight-next-line -exports.handler = verifySession(likeComment); -``` - - - - -```tsx -import KoaRouter from "koa-router"; -import { verifySession } from "supertokens-node/recipe/session/framework/koa"; -import { SessionContext } from "supertokens-node/framework/koa"; - -let router = new KoaRouter(); - -//highlight-start -router.post("/like-comment", verifySession(), (ctx: SessionContext, next) => { - let userId = ctx.session!.getUserId(); - //highlight-end - //.... -}); -``` - - - - -```tsx -import { inject, intercept } from "@loopback/core"; -import { RestBindings, MiddlewareContext, post, response } from "@loopback/rest"; -import { verifySession } from "supertokens-node/recipe/session/framework/loopback"; -import { SessionContext } from "supertokens-node/framework/loopback"; - -class LikeComment { - //highlight-start - constructor(@inject(RestBindings.Http.CONTEXT) private ctx: MiddlewareContext) { } - @post("/like-comment") - @intercept(verifySession()) - @response(200) - handler() { - let userId = (this.ctx as SessionContext).session!.getUserId(); - //highlight-end - //.... - } -} -``` - - - - -```tsx -import { superTokensNextWrapper } from 'supertokens-node/nextjs' -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import { SessionRequest } from "supertokens-node/framework/express"; - -// highlight-start -export default async function likeComment(req: SessionRequest, res: any) { - await superTokensNextWrapper( - async (next) => { - await verifySession()(req, res, next); - }, - req, - res - ) - - let userId = req.session!.getUserId(); - // highlight-end - //.... -} -``` - - - - -```tsx -import { NextResponse, NextRequest } from "next/server"; -import SuperTokens from "supertokens-node"; -import { withSession } from "supertokens-node/nextjs"; -// @ts-ignore -import { backendConfig } from "@/app/config/backend"; - -SuperTokens.init(backendConfig()); - -export function POST(request: NextRequest) { - return withSession(request, async (err, session) => { - if (err) { - return NextResponse.json(err, { status: 500 }); - } - const userId = session!.getUserId(); - //.... - return NextResponse.json({}); - }); -} -``` - - - - -```tsx -import { Controller, Post, UseGuards, Session } from "@nestjs/common"; -import { SessionContainer } from "supertokens-node/recipe/session"; -// @ts-ignore -import { AuthGuard } from './auth/auth.guard'; - -@Controller() -export class ExampleController { - @Post('example') - @UseGuards(new AuthGuard()) // For more information about this guard please read our NestJS guide. - async postExample(@Session() session: SessionContainer): Promise { - //highlight-start - let userId = session.getUserId(); - - //highlight-end - //.... - return true; - } -} -``` - - - - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/supertokens/supertokens-golang/recipe/session" -) - -func main() { - _ = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - // Wrap the API handler in session.VerifySession - session.VerifySession(nil, likeCommentAPI).ServeHTTP(rw, r) - }) -} - -func likeCommentAPI(w http.ResponseWriter, r *http.Request) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(r.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/gin-gonic/gin" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" -) - -func main() { - router := gin.New() - - // Wrap the API handler in session.VerifySession - router.POST("/likecomment", verifySession(nil), likeCommentAPI) -} - -// This is a function that wraps the supertokens verification function -// to work the gin -func verifySession(options *sessmodels.VerifySessionOptions) gin.HandlerFunc { - return func(c *gin.Context) { - session.VerifySession(options, func(rw http.ResponseWriter, r *http.Request) { - c.Request = c.Request.WithContext(r.Context()) - c.Next() - })(c.Writer, c.Request) - // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly - c.Abort() - } -} - -func likeCommentAPI(c *gin.Context) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(c.Request.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/go-chi/chi" - "github.com/supertokens/supertokens-golang/recipe/session" -) - -func main() { - r := chi.NewRouter() - - // Wrap the API handler in session.VerifySession - r.Post("/likecomment", session.VerifySession(nil, likeCommentAPI)) -} - -func likeCommentAPI(w http.ResponseWriter, r *http.Request) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(r.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/gorilla/mux" - "github.com/supertokens/supertokens-golang/recipe/session" -) - -func main() { - router := mux.NewRouter() - - // Wrap the API handler in session.VerifySession - router.HandleFunc("/likecomment", session.VerifySession(nil, likeCommentAPI)).Methods(http.MethodPost) -} - -func likeCommentAPI(w http.ResponseWriter, r *http.Request) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(r.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - - - - - - -```python -from supertokens_python.recipe.session.framework.fastapi import verify_session -from supertokens_python.recipe.session import SessionContainer -from fastapi import Depends - -# highlight-start -@app.post('/like_comment') # type: ignore -async def like_comment(session: SessionContainer = Depends(verify_session())): - user_id = session.get_user_id() - # highlight-end - - print(user_id) -``` - - - - -```python -from supertokens_python.recipe.session.framework.flask import verify_session -from supertokens_python.recipe.session import SessionContainer -from flask import g - -# highlight-start -@app.route('/update-jwt', methods=['POST']) # type: ignore -@verify_session() -def like_comment(): - session: SessionContainer = g.supertokens # type: ignore - - user_id = session.get_user_id() - # highlight-end - - print(user_id) -``` - - - - -```python -from supertokens_python.recipe.session.framework.django.asyncio import verify_session -from django.http import HttpRequest -from supertokens_python.recipe.session import SessionContainer - -# highlight-start -@verify_session() -async def like_comment(request: HttpRequest): - session: SessionContainer = request.supertokens # type: ignore - - user_id = session.get_user_id() - # highlight-end - - print(user_id) -``` - - - - - - - -The `verifySession` function returns a `401` to the frontend if a session doesn't exist, or if the access token has expired, in which case, our frontend SDK automatically refreshes the session. - -In case of successful session verification, you get access to a `session` object using which you can get the user's ID, or manipulate the session information. - - - - - - - -### Microservice authentication - -For authentication between microservices on your backend, checkout the [microservice auth guide](/docs/microservice_auth/client-credentials). - - - -## Protecting website routes - - - - - -:::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. - -::: - - - - -You can wrap your components with the `` react component. This will ensure that your component renders only if the user is logged in. If they are not logged in, the user will be redirected to the login page. - -```tsx -import React from "react"; -import { - BrowserRouter, - Routes, - Route, -} from "react-router-dom"; -// highlight-next-line -import { SessionAuth } from "supertokens-auth-react/recipe/session"; -// @ts-ignore -import MyDashboardComponent from "./dashboard"; - -class App extends React.Component { - render() { - return ( - - - - {/*Components that require to be protected by authentication*/} - - - // highlight-end - } /> - - - ); - } -} -``` - - - - - -You can use the `doesSessionExist` function to check if a session exists in all your routes. - -```tsx -import Session from 'supertokens-web-js/recipe/session'; - -async function doesSessionExist() { - if (await Session.doesSessionExist()) { - // user is logged in - } else { - // user has not logged in yet - } -} -``` - - - - - - - - - - - -## See also - -- Optional sessions [for APIs](../common-customizations/sessions/session-verification-in-api/verify-session#optional-session-verification) and [the frontend](../common-customizations/sessions/checking-session-front-end) -- [Verifying session without using a middleware](../common-customizations/sessions/session-verification-in-api/get-session) -- Session claim validation [for APIs](../common-customizations/sessions/claims/claim-validators) and [the frontend](../common-customizations/sessions/claims/claim-validators) -- [Changing session lifetime](../common-customizations/sessions/change-session-timeout) -- [Sharing session across sub domains](../common-customizations/sessions/share-sessions-across-sub-domains) - - diff --git a/v2/emailpassword/pre-built-ui/setup/backend.mdx b/v2/emailpassword/pre-built-ui/setup/backend.mdx index c5d281e21..577fc5a32 100644 --- a/v2/emailpassword/pre-built-ui/setup/backend.mdx +++ b/v2/emailpassword/pre-built-ui/setup/backend.mdx @@ -4,912 +4,8 @@ title: "Step 2: Backend" hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import TabItem from '@theme/TabItem'; -import NodeJSFrameworkSubTabsServerless from "/src/components/tabs/NodeJSFrameworkSubTabsServerless" -import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs" -import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" -import AppInfoForm from "/src/components/appInfoForm" -import CoreInjector from "/src/components/coreInjector" -import {Question, Answer}from "/src/components/question" -import BackendSDKCasing from "/src/components/BackendSDKCasing" -# Backend Integration + -## Supported frameworks - -import TechStackSupport from "../../../community/reusableMD/supported-tech-stacks-backend.mdx" - - - -## 1) Install - - - - -```bash -npm i -s supertokens-node -``` - - - - -```bash -go get github.com/supertokens/supertokens-golang -``` - - - - -```bash -pip install supertokens-python -``` - - - - -## 2) Initialise SuperTokens - - - - - -Add the code below to your server's init file. - - - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import EmailPassword from "supertokens-node/recipe/emailpassword"; - -supertokens.init({ - framework: "express", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}", - }, - recipeList: [ - EmailPassword.init(), // initializes signin / sign up features - Session.init() // initializes session features - ] -}); -``` - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import EmailPassword from "supertokens-node/recipe/emailpassword"; - -supertokens.init({ - framework: "hapi", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}", - }, - recipeList: [ - EmailPassword.init(), // initializes signin / sign up features - Session.init() // initializes session features - ] -}); -``` - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import EmailPassword from "supertokens-node/recipe/emailpassword"; - -supertokens.init({ - framework: "fastify", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}", - }, - recipeList: [ - EmailPassword.init(), // initializes signin / sign up features - Session.init() // initializes session features - ] -}); -``` - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import EmailPassword from "supertokens-node/recipe/emailpassword"; - -supertokens.init({ - framework: "koa", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}", - }, - recipeList: [ - EmailPassword.init(), // initializes signin / sign up features - Session.init() // initializes session features - ] -}); -``` - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import EmailPassword from "supertokens-node/recipe/emailpassword"; - -supertokens.init({ - framework: "loopback", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}", - }, - recipeList: [ - EmailPassword.init(), // initializes signin / sign up features - Session.init() // initializes session features - ] -}); -``` - - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/emailpassword" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - apiBasePath := "^{form_apiBasePath}" - websiteBasePath := "^{form_websiteBasePath}" - err := supertokens.Init(supertokens.TypeInput{ - Supertokens: &supertokens.ConnectionInfo{ - ^{coreInjector_connection_uri_comment} - ConnectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}APIKey: ^{coreInjector_api_key}, - }, - AppInfo: supertokens.AppInfo{ - AppName: "^{form_appName}", - APIDomain: "^{form_apiDomain}", - WebsiteDomain: "^{form_websiteDomain}", - APIBasePath: &apiBasePath, - WebsiteBasePath: &websiteBasePath, - }, - RecipeList: []supertokens.Recipe{ - emailpassword.Init(nil), - session.Init(nil), - }, - }) - - if err != nil { - panic(err.Error()) - } -} -``` - - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig -from supertokens_python.recipe import emailpassword, session - -init( - app_info=InputAppInfo( - app_name="^{form_appName}", - api_domain="^{form_apiDomain}", - website_domain="^{form_websiteDomain}", - api_base_path="^{form_apiBasePath}", - website_base_path="^{form_websiteBasePath}" - ), - supertokens_config=SupertokensConfig( - ^{coreInjector_connection_uri_comment_with_hash} - connection_uri=^{coreInjector_uri} - ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} - ), - framework='fastapi', - recipe_list=[ - session.init(), # initializes session features - emailpassword.init() - ], - mode='asgi' # use wsgi if you are running using gunicorn -) -``` - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig -from supertokens_python.recipe import emailpassword, session - -init( - app_info=InputAppInfo( - app_name="^{form_appName}", - api_domain="^{form_apiDomain}", - website_domain="^{form_websiteDomain}", - api_base_path="^{form_apiBasePath}", - website_base_path="^{form_websiteBasePath}" - ), - supertokens_config=SupertokensConfig( - ^{coreInjector_connection_uri_comment_with_hash} - connection_uri=^{coreInjector_uri} - ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} - ), - framework='flask', - recipe_list=[ - session.init(), # initializes session features - emailpassword.init() - ] -) -``` - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig -from supertokens_python.recipe import emailpassword, session - -init( - app_info=InputAppInfo( - app_name="^{form_appName}", - api_domain="^{form_apiDomain}", - website_domain="^{form_websiteDomain}", - api_base_path="^{form_apiBasePath}", - website_base_path="^{form_websiteBasePath}" - ), - supertokens_config=SupertokensConfig( - ^{coreInjector_connection_uri_comment_with_hash} - connection_uri=^{coreInjector_uri} - ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} - ), - framework='django', - recipe_list=[ - session.init(), # initializes session features - emailpassword.init() - ], - mode='asgi' # use wsgi if you are running django server in sync mode -) -``` - - - - - - - - - - -## 3) Add the SuperTokens APIs & CORS setup - - - - - - - - -:::important -- Add the `middleware` BEFORE all your routes. -- Add the `cors` middleware BEFORE the SuperTokens middleware as shown below. -::: - -```tsx -import express from "express"; -import cors from "cors"; -import supertokens from "supertokens-node"; -import { middleware } from "supertokens-node/framework/express"; - -let app = express(); - -app.use(cors({ - // highlight-start - origin: "^{form_websiteDomain}", - allowedHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], - credentials: true, - // highlight-end -})); - -// IMPORTANT: CORS should be before the below line. -// highlight-next-line -app.use(middleware()); - -// ...your API routes -``` -This `middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): - -- `POST ^{form_apiBasePath}/signup`: For signing up a user with email & password -- `POST ^{form_apiBasePath}/signin`: For signing in a user with email & password - - - - -Register the `plugin`. - -```tsx -import Hapi from "@hapi/hapi"; -import supertokens from "supertokens-node"; -import { plugin } from "supertokens-node/framework/hapi"; - -let server = Hapi.server({ - port: 8000, - routes: { - // highlight-start - cors: { - origin: ["^{form_websiteDomain}"], - additionalHeaders: [...supertokens.getAllCORSHeaders()], - credentials: true, - } - // highlight-end - } -}); - -(async () => { - // highlight-next-line - await server.register(plugin); - - await server.start(); -})(); - -// ...your API routes -``` -This `plugin` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)) as well take care of all the errors thrown by the Supertokens library: - -- `POST ^{form_apiBasePath}/signup`: For signing up a user with email & password -- `POST ^{form_apiBasePath}/signin`: For signing in a user with email & password - - - - -Register the `plugin`. Also register [`@fastify/formbody`](https://github.com/fastify/fastify-formbody) plugin. - -```tsx -import cors from "@fastify/cors"; -import supertokens from "supertokens-node"; -import { plugin } from "supertokens-node/framework/fastify"; -import formDataPlugin from "@fastify/formbody"; - -import fastifyImport from "fastify"; - -let fastify = fastifyImport(); - -// ...other middlewares -// highlight-start -fastify.register(cors, { - origin: "^{form_websiteDomain}", - allowedHeaders: ['Content-Type', ...supertokens.getAllCORSHeaders()], - credentials: true, -}); -// highlight-end - -(async () => { - // highlight-next-line - await fastify.register(formDataPlugin); - // highlight-next-line - await fastify.register(plugin); - - await fastify.listen(8000); -})(); - -// ...your API routes -``` -This `plugin` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): - -- `POST ^{form_apiBasePath}/signup`: For signing up a user with email & password -- `POST ^{form_apiBasePath}/signin`: For signing in a user with email & password - - - - -:::important -Add the `middleware` BEFORE all your routes. -::: - -```tsx -import Koa from "koa"; -import cors from '@koa/cors'; -import supertokens from "supertokens-node"; -import { middleware } from "supertokens-node/framework/koa"; - -let app = new Koa(); - -app.use(cors({ - // highlight-start - origin: "^{form_websiteDomain}", - allowHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], - credentials: true, - // highlight-end -})); - -// highlight-next-line -app.use(middleware()); - -// ...your API routes -``` -This `middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): - -- `POST ^{form_apiBasePath}/signup`: For signing up a user with email & password -- `POST ^{form_apiBasePath}/signin`: For signing in a user with email & password - - - - -:::important -Add the `middleware` BEFORE all your routes. -::: - -```tsx -import { RestApplication } from "@loopback/rest"; -import supertokens from "supertokens-node"; -import { middleware } from "supertokens-node/framework/loopback"; - -let app = new RestApplication({ - rest: { - cors: { - // highlight-start - origin: "^{form_websiteDomain}", - allowedHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], - credentials: true - // highlight-end - } - } -}); - -// highlight-next-line -app.middleware(middleware); - -// ...your API routes -``` -This `middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): - -- `POST ^{form_apiBasePath}/signup`: For signing up a user with email & password -- `POST ^{form_apiBasePath}/signin`: For signing in a user with email & password - - - - - - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "net/http" - "strings" - - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - //highlight-start - http.ListenAndServe("SERVER ADDRESS", corsMiddleware( - supertokens.Middleware(http.HandlerFunc(func(rw http.ResponseWriter, - //highlight-end - r *http.Request) { - // TODO: Handle your APIs.. - - })))) -} - -func corsMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(response http.ResponseWriter, r *http.Request) { - response.Header().Set("Access-Control-Allow-Origin", "^{form_websiteDomain}") - response.Header().Set("Access-Control-Allow-Credentials", "true") - if r.Method == "OPTIONS" { - // we add content-type + other headers used by SuperTokens - response.Header().Set("Access-Control-Allow-Headers", - strings.Join(append([]string{"Content-Type"}, - //highlight-start - supertokens.GetAllCORSHeaders()...), ",")) - //highlight-end - response.Header().Set("Access-Control-Allow-Methods", "*") - response.Write([]byte("")) - } else { - next.ServeHTTP(response, r) - } - }) -} -``` - - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "net/http" - - "github.com/gin-contrib/cors" - "github.com/gin-gonic/gin" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - router := gin.New() - - // CORS - router.Use(cors.New(cors.Config{ - AllowOrigins: []string{"^{form_websiteDomain}"}, - AllowMethods: []string{"GET", "POST", "DELETE", "PUT", "OPTIONS"}, - AllowHeaders: append([]string{"content-type"}, - // highlight-next-line - supertokens.GetAllCORSHeaders()...), - AllowCredentials: true, - })) - - // Adding the SuperTokens middleware - // highlight-start - router.Use(func(c *gin.Context) { - supertokens.Middleware(http.HandlerFunc( - func(rw http.ResponseWriter, r *http.Request) { - c.Next() - })).ServeHTTP(c.Writer, c.Request) - // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly - c.Abort() - }) - // highlight-end - - // Add APIs and start server -} -``` - - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "github.com/go-chi/chi" - "github.com/go-chi/cors" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - r := chi.NewRouter() - - // CORS - r.Use(cors.Handler(cors.Options{ - AllowedOrigins: []string{"^{form_websiteDomain}"}, - AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, - AllowedHeaders: append([]string{"Content-Type"}, - //highlight-next-line - supertokens.GetAllCORSHeaders()...), - AllowCredentials: true, - })) - - // SuperTokens Middleware - //highlight-next-line - r.Use(supertokens.Middleware) - - // Add APIs and start server -} -``` - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "net/http" - - "github.com/gorilla/handlers" - "github.com/gorilla/mux" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - // TODO: Add APIs - - router := mux.NewRouter() - - // Adding handlers.CORS(options)(supertokens.Middleware(router))) - //highlight-start - http.ListenAndServe("SERVER ADDRESS", handlers.CORS( - handlers.AllowedHeaders(append([]string{"Content-Type"}, - supertokens.GetAllCORSHeaders()...)), - handlers.AllowedMethods([]string{"GET", "POST", "PUT", "HEAD", "OPTIONS"}), - handlers.AllowedOrigins([]string{"^{form_websiteDomain}"}), - handlers.AllowCredentials(), - )(supertokens.Middleware(router))) - //highlight-end -} -``` - - - -This `Middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): - -- `POST ^{form_apiBasePath}/signup`: For signing up a user with email & password -- `POST ^{form_apiBasePath}/signin`: For signing in a user with email & password - - - - - - -Use the `Middleware` (**BEFORE all your routes**) and the `get_all_cors_headers()` functions as shown below. - -```python -from supertokens_python import get_all_cors_headers -from fastapi import FastAPI -from starlette.middleware.cors import CORSMiddleware -from supertokens_python.framework.fastapi import get_middleware - -app = FastAPI() -# highlight-next-line -app.add_middleware(get_middleware()) - -# TODO: Add APIs - -app.add_middleware( - CORSMiddleware, - allow_origins=[ - "^{form_websiteDomain}" - ], - allow_credentials=True, - allow_methods=["GET", "PUT", "POST", "DELETE", "OPTIONS", "PATCH"], - # highlight-next-line - allow_headers=["Content-Type"] + get_all_cors_headers(), -) - -# TODO: start server -``` - - - - -- Use the `Middleware` (**BEFORE all your routes and after calling init function**) and the `get_all_cors_headers()` functions as shown below. -- Add a route to catch all paths and return a 404. This is needed because if we don't add this, then OPTIONS request for the APIs exposed by the `Middleware` will return a `404`. - -```python -from supertokens_python import get_all_cors_headers -from flask import Flask, abort -from flask_cors import CORS # type: ignore -from supertokens_python.framework.flask import Middleware - -app = Flask(__name__) -# highlight-next-line -Middleware(app) - -# TODO: Add APIs - -CORS( - app=app, - origins=[ - "^{form_websiteDomain}" - ], - supports_credentials=True, - # highlight-next-line - allow_headers=["Content-Type"] + get_all_cors_headers(), -) - -# This is required since if this is not there, then OPTIONS requests for -# the APIs exposed by the supertokens' Middleware will return a 404 -# highlight-start -@app.route('/', defaults={'u_path': ''}) # type: ignore -@app.route('/') # type: ignore -def catch_all(u_path: str): - abort(404) -# highlight-end - -# TODO: start server -``` - - - - -Use the `Middleware` and the `get_all_cors_headers()` functions as shown below in your `settings.py`. - -```python -from supertokens_python import get_all_cors_headers -from typing import List -from corsheaders.defaults import default_headers - -CORS_ORIGIN_WHITELIST = [ - "^{form_websiteDomain}" -] - -CORS_ALLOW_CREDENTIALS = True - -CORS_ALLOWED_ORIGINS = [ - "^{form_websiteDomain}" -] - -CORS_ALLOW_HEADERS: List[str] = list(default_headers) + [ - "Content-Type" - # highlight-next-line -] + get_all_cors_headers() - -INSTALLED_APPS = [ - 'corsheaders', - 'supertokens_python' -] - -MIDDLEWARE = [ - 'corsheaders.middleware.CorsMiddleware', - ..., - # highlight-next-line - 'supertokens_python.framework.django.django_middleware.middleware', -] -# TODO: start server -``` - - - - -This `Middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): - -- `POST ^{form_apiBasePath}/signup`: For signing up a user with email & password -- `POST ^{form_apiBasePath}/signin`: For signing in a user with email & password - - - - - - - -## 4) Add the SuperTokens error handler - - - - - - -```tsx -import express, { Request, Response, NextFunction } from 'express'; -import { errorHandler } from "supertokens-node/framework/express"; - -let app = express(); - -// ...your API routes - -// highlight-start -// Add this AFTER all your routes -app.use(errorHandler()) -// highlight-end - -// your own error handler -app.use((err: unknown, req: Request, res: Response, next: NextFunction) => { /* ... */ }); -``` - - - -No additional `errorHandler` is required. - - - - -Add the `errorHandler` **Before all your routes and plugin registration** - -```tsx -import Fastify from "fastify"; -import { errorHandler } from "supertokens-node/framework/fastify"; - -let fastify = Fastify(); - -// highlight-next-line -fastify.setErrorHandler(errorHandler()); - -// ...your API routes -``` - - - -No additional `errorHandler` is required. - - - - -No additional `errorHandler` is required. - - - - - - -:::info -You can skip this step -::: - - - - -:::info -You can skip this step -::: - - - - -## 5) Setup the SuperTokens core - -You need to now setup an instance of the SuperTokens core for your app (that your backend should connect to). You have two options: -- [Managed service](./core/saas-setup) -- Self hosted with your own database ([With Docker](./core/with-docker) or [Without Docker](./core/without-docker)) \ No newline at end of file diff --git a/v2/emailpassword/pre-built-ui/setup/frontend.mdx b/v2/emailpassword/pre-built-ui/setup/frontend.mdx index 3cfc4901e..bac6ce88b 100644 --- a/v2/emailpassword/pre-built-ui/setup/frontend.mdx +++ b/v2/emailpassword/pre-built-ui/setup/frontend.mdx @@ -4,655 +4,8 @@ title: "Step 1: Frontend" hide_title: true --- - - - +import Redirector from '/src/components/Redirector'; -import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" -import TabItem from '@theme/TabItem'; -import {Question, Answer}from "/src/components/question" -import AppInfoForm from "/src/components/appInfoForm" -import RRDVersionSubTabs from "/src/components/tabs/RRDVersionSubTabs" -import NpmVersionOrYarnSubTabs from "/src/components/tabs/NpmVersionOrYarnSubTabs" -import PreBuiltUIInjector from "/src/components/prebuiltuiInjector" -# Frontend Integration + -## Supported frameworks - -import TechStackSupport from "../../../community/reusableMD/supported-tech-stacks-frontend.mdx" - - - -# Automatic setup using CLI - -Run the following command in your terminal. -```bash -npx create-supertokens-app@latest --recipe=^{docsLinkRecipeName} -``` - -Once this is done, you can skip Step (1) and (2) in this section (see left nav bar) and move directly to setting up the SuperTokens core (Step 3). - -Or, you can manually integrate SuperTokens by following the steps below. - -# Manual setup steps below - -## 1) Install - - - - - - - - -```bash -npm i -s supertokens-auth-react -``` - - - - -```bash -npm i -s supertokens-auth-react supertokens-web-js -``` - - - - -```bash -yarn add supertokens-auth-react supertokens-web-js -``` - - - - - - - - - -Start by installing the SuperTokens Web SDK: - -```bash -npm i -s supertokens-web-js -``` - - - - - -Start by installing the SuperTokens Web SDK: - -```bash -npm i -s supertokens-web-js -``` - - - - - -Start by installing the SuperTokens Web SDK: - -```bash -yarn add supertokens-web-js -``` - - - - - - - - - - - -Start by installing the SuperTokens web SDK: - -```bash -npm i -s supertokens-web-js -``` - - - - - -Start by installing the SuperTokens web SDK: - -```bash -npm i -s supertokens-web-js -``` - - - - - -Start by installing the SuperTokens web SDK: - -```bash -yarn add supertokens-web-js -``` - - - - - - - - -:::important -SuperTokens does not support pre-built UI for mobile frameworks. Please refer to the [setup guide for custom UI](../../custom-ui/init/frontend) to integrate SuperTokens in your app. -::: - - - - - - - -## 2) Call the `init` function - - - - - - -In your `App.js` file, import SuperTokens and call the `init` function - -```tsx -import React from 'react'; - -// highlight-start -import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; -import EmailPassword from "supertokens-auth-react/recipe/emailpassword"; -import Session from "supertokens-auth-react/recipe/session"; - -SuperTokens.init({ - appInfo: { - // learn more about this on https://supertokens.com/docs/emailpassword/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}", - }, - recipeList: [ - EmailPassword.init(), - Session.init() - ] -}); -// highlight-end - - -/* Your App */ -class App extends React.Component { - render() { - return ( - // highlight-next-line - - {/*Your app components*/} - // highlight-next-line - - ); - } -} -``` - - - - - - - - - - - -Before we initialize the `supertokens-web-js` SDK let's see how we will use it in our Angular app - -**Architecture** - -- The `supertokens-web-js` SDK is responsible for session management and providing helper functions to check if a session exists, or validate the access token claims on the frontend (for example, to check for user roles before showing some UI). We will initialise this SDK on the root of your Angular app, so that all pages in your app can use it. -- We will create a `^{form_websiteBasePath}*` route in the Angular app which will render our pre built UI which will also need to be initialised, but only on that route. - -**Creating the `^{form_websiteBasePath}` route** - -- Use the Angular CLI to generate a new route - - ```bash - ng generate module auth --route auth --module app.module - ``` - -- Add the following code to your `auth` angular component - - ```tsx title="/app/auth/auth.component.ts" - import {init as supertokensUIInit} from "supertokens-auth-react-script"; - import supertokensUIEmailPassword from "supertokens-auth-react-script/recipe/emailpassword"; - import supertokensUISession from "supertokens-auth-react-script/recipe/session"; - 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: [ - supertokensUIEmailPassword.init(), - supertokensUISession.init(), - ], - }); - } - this.renderer.appendChild(this.document.body, script); - } - } - ``` - - In the `loadScript` function, we provide the SuperTokens config for the UI. We add the emailpassword and session recipe. - -- Initialize the `supertokens-web-js` SDK in your angular app's root component. This will provide session management across your entire application. - - ```tsx title="/app/app.component.ts " - import SuperTokens from "supertokens-web-js"; - import Session from "supertokens-web-js/recipe/session"; - - SuperTokens.init({ - appInfo: { - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}", - }, - recipeList: [ - Session.init(), - ], - }); - ``` - -
- -
- -
- - - - - - - -Before we initialize the `supertokens-web-js` SDK let's see how we will use it in our Vue app - -**Architecture** - -- The `supertokens-web-js` SDK is responsible for session management and providing helper functions to check if a session exists, or validate the access token claims on the frontend (for example, to check for user roles before showing some UI). We will initialise this SDK on the root of your Vue app, so that all pages in your app can use it. -- We will create a `^{form_websiteBasePath}*` route in the Vue app which will render our pre built UI which will also need to be initialised, but only on that route. - -**Creating the `^{form_websiteBasePath}` route** - -- Create a new file `AuthView.vue`, this Vue component will be used to render the auth component: - ```tsx - import {init as supertokensUIInit} from "supertokens-auth-react-script"; - import supertokensUIEmailPassword from "supertokens-auth-react-script/recipe/emailpassword"; - import supertokensUISession from "supertokens-auth-react-script/recipe/session"; - - - - ``` - - In the `loadScript` function, we provide the SuperTokens config for the UI. We add the emailpassword and session recipe. - -- Initialize the `supertokens-web-js` SDK in your Vue app's `main.ts` file. This will provide session management across your entire application. - - ```tsx title="/main.ts " - // @ts-ignore - import { createApp } from "vue"; - import SuperTokens from "supertokens-web-js"; - import Session from "supertokens-web-js/recipe/session"; - // @ts-ignore - import App from "./App.vue"; - // @ts-ignore - import router from "./router"; - - SuperTokens.init({ - appInfo: { - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}", - }, - recipeList: [ - Session.init(), - ], - }); - - const app = createApp(App); - - app.use(router); - - app.mount("#app"); - - ``` - - - - - - - - - -:::important -SuperTokens does not support pre-built UI for mobile frameworks. Please refer to the [setup guide for custom UI](../../custom-ui/init/frontend) to integrate SuperTokens in your app. -::: - - - -
- - - - - -## 3) Setup Routing to show the login UI - - - - - - - - - - - -Call the `getSuperTokensRoutesForReactRouterDom` method from within any `react-router-dom` `Routes` component. - -```tsx -import React from 'react'; -import { - BrowserRouter, - Routes, - Route, - Link -} from "react-router-dom"; - -// highlight-next-line -import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; -import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui"; -^{prebuiltuiimport} -import * as reactRouterDom from "react-router-dom"; - -class App extends React.Component { - render() { - return ( - - - - {/*This renders the login UI on the ^{form_websiteBasePath} route*/} - // highlight-next-line - {getSuperTokensRoutesForReactRouterDom(reactRouterDom, [^{recipePreBuiltUINameCapitalLetters}])} - {/*Your app routes*/} - - - - ); - } -} -``` - - - - - -Call the `getSuperTokensRoutesForReactRouterDom` method from within any `react-router-dom` `Switch` component. - -```tsx -import React from 'react'; -import { - BrowserRouter, - Switch, - Route, - Link -} from "react-router-dom5"; - -// highlight-next-line -import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; -import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui"; -^{prebuiltuiimport} -import * as reactRouterDom from "react-router-dom"; - - -class App extends React.Component { - render() { - return ( - - - - {/*This renders the login UI on the ^{form_websiteBasePath} route*/} - // highlight-next-line - {getSuperTokensRoutesForReactRouterDom(reactRouterDom, [^{recipePreBuiltUINameCapitalLetters}])} - {/*Your app routes*/} - - - - ); - } -} -``` - - - - - - - - -Add the highlighted code snippet to your root level `render` function. - -```tsx -import React from 'react'; -^{prebuiltuiimport} -import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; -import { canHandleRoute, getRoutingComponent } from "supertokens-auth-react/ui"; - -class App extends React.Component { - render() { - // highlight-start - if (canHandleRoute([^{recipePreBuiltUINameCapitalLetters}])) { - // This renders the login UI on the ^{form_websiteBasePath} route - return getRoutingComponent([^{recipePreBuiltUINameCapitalLetters}]) - } - // highlight-end - - return ( - {/*Your app*/} - ); - } - -} -``` - - - - - - - - - - -Update your angular router so that all auth related requests load the `auth` component - -```tsx title="/app/app-routing.module.ts" -import { NgModule } from "@angular/core"; -import { RouterModule, Routes } from "@angular/router"; - -const routes: Routes = [ - // highlight-start - { - path: "^{form_websiteBasePath_withoutForwardSlash}", - // @ts-ignore - loadChildren: () => import("./auth/auth.module").then((m) => m.AuthModule), - }, - - // @ts-ignore - { path: "**", loadChildren: () => import("./home/home.module").then((m) => m.HomeModule) }, - // highlight-end -]; - -@NgModule({ - imports: [RouterModule.forRoot(routes)], - exports: [RouterModule], -}) -export class AppRoutingModule {} -``` - - - - - - - - -Update your Vue router so that all auth related requests load the `AuthView` component - -```tsx title="/router/index.ts" -// @ts-ignore -import { createRouter, createWebHistory } from "vue-router"; -// @ts-ignore -import HomeView from "../views/HomeView.vue"; -// @ts-ignore -import AuthView from "../views/AuthView.vue"; - -const router = createRouter({ - // @ts-ignore - history: createWebHistory(import.meta.env.BASE_URL), - routes: [ - { - path: "/", - name: "home", - component: HomeView, - }, - { - path: "^{form_websiteBasePath}/:pathMatch(.*)*", - name: "auth", - component: AuthView, - }, - ], -}); - -export default router; -``` - - - - - - - -:::important -SuperTokens does not support pre-built UI for mobile frameworks. Please refer to the [setup guide for custom UI](../../custom-ui/init/frontend) to integrate SuperTokens in your app. -::: - - - - - - - -## 4) View the login UI - - -^{form_addVisitWebsiteBasePathText} - - - -At this stage, you've successfully integrated your website with SuperTokens. The next section will guide you through setting up your backend. - \ No newline at end of file diff --git a/v2/emailpassword/quickstart/backend-setup.mdx b/v2/emailpassword/quickstart/backend-setup.mdx new file mode 100644 index 000000000..1786a414a --- /dev/null +++ b/v2/emailpassword/quickstart/backend-setup.mdx @@ -0,0 +1,1258 @@ +--- +id: backend-setup +title: Backend Setup +hide_title: true +show_ui_switcher: false +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +import PreBuiltUIInjector from "/src/components/prebuiltuiInjector" +import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; +import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" +import {Question, Answer}from "/src/components/question" +import RRDVersionSubTabs from "/src/components/tabs/RRDVersionSubTabs" +import AppInfoForm from "/src/components/appInfoForm" +import NodeJSFrameworkSubTabsServerless from "/src/components/tabs/NodeJSFrameworkSubTabsServerless" +import CoreInjector from "/src/components/coreInjector" +import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs" +import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" +import BackendSDKCasing from "/src/components/BackendSDKCasing" + + + +# Backend Setup + +Let's got through the changes required so that your backend can expose the **SuperTokens** authentication features. + +## 1. Install the SDK + + + + + +Run the following command in your terminal to install the package. + + + + +```bash +npm i -s supertokens-node +``` + + + + +```bash +go get github.com/supertokens/supertokens-golang +``` + + + + +```bash +pip install supertokens-python +``` + + + + + + +## 2. Initialize the SDK + +You will have to intialize the **Backend SDK** alongside the code that starts your server. +The init call will include [configuration details](../appinfo) for your app, how the backend will connect to the **SuperTokens Core**, as well as the **Recipes** that will be used in your setup. + + + + + + + + + + +```tsx +import supertokens from "supertokens-node"; +import Session from "supertokens-node/recipe/session"; +import EmailPassword from "supertokens-node/recipe/emailpassword"; + +supertokens.init({ + framework: "express", + supertokens: { + ^{coreInjector_connection_uri_comment} + connectionURI: ^{coreInjector_uri} + ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, + }, + appInfo: { + // learn more about this on https://supertokens.com/docs/session/appinfo + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + websiteDomain: "^{form_websiteDomain}", + apiBasePath: "^{form_apiBasePath}", + websiteBasePath: "^{form_websiteBasePath}", + }, + recipeList: [ + EmailPassword.init(), // initializes signin / sign up features + Session.init() // initializes session features + ] +}); +``` + + + + +```tsx +import supertokens from "supertokens-node"; +import Session from "supertokens-node/recipe/session"; +import EmailPassword from "supertokens-node/recipe/emailpassword"; + +supertokens.init({ + framework: "hapi", + supertokens: { + ^{coreInjector_connection_uri_comment} + connectionURI: ^{coreInjector_uri} + ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, + }, + appInfo: { + // learn more about this on https://supertokens.com/docs/session/appinfo + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + websiteDomain: "^{form_websiteDomain}", + apiBasePath: "^{form_apiBasePath}", + websiteBasePath: "^{form_websiteBasePath}", + }, + recipeList: [ + EmailPassword.init(), // initializes signin / sign up features + Session.init() // initializes session features + ] +}); +``` + + + + +```tsx +import supertokens from "supertokens-node"; +import Session from "supertokens-node/recipe/session"; +import EmailPassword from "supertokens-node/recipe/emailpassword"; + +supertokens.init({ + framework: "fastify", + supertokens: { + ^{coreInjector_connection_uri_comment} + connectionURI: ^{coreInjector_uri} + ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, + }, + appInfo: { + // learn more about this on https://supertokens.com/docs/session/appinfo + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + websiteDomain: "^{form_websiteDomain}", + apiBasePath: "^{form_apiBasePath}", + websiteBasePath: "^{form_websiteBasePath}", + }, + recipeList: [ + EmailPassword.init(), // initializes signin / sign up features + Session.init() // initializes session features + ] +}); +``` + + + + +```tsx +import supertokens from "supertokens-node"; +import Session from "supertokens-node/recipe/session"; +import EmailPassword from "supertokens-node/recipe/emailpassword"; + +supertokens.init({ + framework: "koa", + supertokens: { + ^{coreInjector_connection_uri_comment} + connectionURI: ^{coreInjector_uri} + ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, + }, + appInfo: { + // learn more about this on https://supertokens.com/docs/session/appinfo + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + websiteDomain: "^{form_websiteDomain}", + apiBasePath: "^{form_apiBasePath}", + websiteBasePath: "^{form_websiteBasePath}", + }, + recipeList: [ + EmailPassword.init(), // initializes signin / sign up features + Session.init() // initializes session features + ] +}); +``` + + + + +```tsx +import supertokens from "supertokens-node"; +import Session from "supertokens-node/recipe/session"; +import EmailPassword from "supertokens-node/recipe/emailpassword"; + +supertokens.init({ + framework: "loopback", + supertokens: { + ^{coreInjector_connection_uri_comment} + connectionURI: ^{coreInjector_uri} + ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, + }, + appInfo: { + // learn more about this on https://supertokens.com/docs/session/appinfo + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + websiteDomain: "^{form_websiteDomain}", + apiBasePath: "^{form_apiBasePath}", + websiteBasePath: "^{form_websiteBasePath}", + }, + recipeList: [ + EmailPassword.init(), // initializes signin / sign up features + Session.init() // initializes session features + ] +}); +``` + + + + + +```go +import ( + "github.com/supertokens/supertokens-golang/recipe/emailpassword" + "github.com/supertokens/supertokens-golang/recipe/session" + "github.com/supertokens/supertokens-golang/supertokens" +) + +func main() { + apiBasePath := "^{form_apiBasePath}" + websiteBasePath := "^{form_websiteBasePath}" + err := supertokens.Init(supertokens.TypeInput{ + Supertokens: &supertokens.ConnectionInfo{ + ^{coreInjector_connection_uri_comment} + ConnectionURI: ^{coreInjector_uri} + ^{coreInjector_api_key_commented}APIKey: ^{coreInjector_api_key}, + }, + AppInfo: supertokens.AppInfo{ + AppName: "^{form_appName}", + APIDomain: "^{form_apiDomain}", + WebsiteDomain: "^{form_websiteDomain}", + APIBasePath: &apiBasePath, + WebsiteBasePath: &websiteBasePath, + }, + RecipeList: []supertokens.Recipe{ + emailpassword.Init(nil), + session.Init(nil), + }, + }) + + if err != nil { + panic(err.Error()) + } +} +``` + + + + + +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import emailpassword, session + +init( + app_info=InputAppInfo( + app_name="^{form_appName}", + api_domain="^{form_apiDomain}", + website_domain="^{form_websiteDomain}", + api_base_path="^{form_apiBasePath}", + website_base_path="^{form_websiteBasePath}" + ), + supertokens_config=SupertokensConfig( + ^{coreInjector_connection_uri_comment_with_hash} + connection_uri=^{coreInjector_uri} + ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} + ), + framework='fastapi', + recipe_list=[ + session.init(), # initializes session features + emailpassword.init() + ], + mode='asgi' # use wsgi if you are running using gunicorn +) +``` + + + + +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import emailpassword, session + +init( + app_info=InputAppInfo( + app_name="^{form_appName}", + api_domain="^{form_apiDomain}", + website_domain="^{form_websiteDomain}", + api_base_path="^{form_apiBasePath}", + website_base_path="^{form_websiteBasePath}" + ), + supertokens_config=SupertokensConfig( + ^{coreInjector_connection_uri_comment_with_hash} + connection_uri=^{coreInjector_uri} + ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} + ), + framework='flask', + recipe_list=[ + session.init(), # initializes session features + emailpassword.init() + ] +) +``` + + + + +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import emailpassword, session + +init( + app_info=InputAppInfo( + app_name="^{form_appName}", + api_domain="^{form_apiDomain}", + website_domain="^{form_websiteDomain}", + api_base_path="^{form_apiBasePath}", + website_base_path="^{form_websiteBasePath}" + ), + supertokens_config=SupertokensConfig( + ^{coreInjector_connection_uri_comment_with_hash} + connection_uri=^{coreInjector_uri} + ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} + ), + framework='django', + recipe_list=[ + session.init(), # initializes session features + emailpassword.init() + ], + mode='asgi' # use wsgi if you are running django server in sync mode +) +``` + + + + + + + + + + +## 3. Add the SuperTokens APIs and Configure CORS + + + + + +Now that the SDK is initialized you need to expose the endpoints that will be used by the frontend SDKs. +Besides this, your server's CORS, Cross-Origin Resource Sharing, settings should be updated to allow the use of the authentication headers required by **SuperTokens**. + + + + + + + + +:::important +- Add the `middleware` BEFORE all your routes. +- Add the `cors` middleware BEFORE the SuperTokens middleware as shown below. +::: + +```tsx +import express from "express"; +import cors from "cors"; +import supertokens from "supertokens-node"; +import { middleware } from "supertokens-node/framework/express"; + +let app = express(); + +app.use(cors({ + // highlight-start + origin: "^{form_websiteDomain}", + allowedHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], + credentials: true, + // highlight-end +})); + +// IMPORTANT: CORS should be before the below line. +// highlight-next-line +app.use(middleware()); + +// ...your API routes +``` + + + + +Register the `plugin`. + +```tsx +import Hapi from "@hapi/hapi"; +import supertokens from "supertokens-node"; +import { plugin } from "supertokens-node/framework/hapi"; + +let server = Hapi.server({ + port: 8000, + routes: { + // highlight-start + cors: { + origin: ["^{form_websiteDomain}"], + additionalHeaders: [...supertokens.getAllCORSHeaders()], + credentials: true, + } + // highlight-end + } +}); + +(async () => { + // highlight-next-line + await server.register(plugin); + + await server.start(); +})(); + +// ...your API routes +``` + + + + +Register the `plugin`. Also register [`@fastify/formbody`](https://github.com/fastify/fastify-formbody) plugin. + +```tsx +import cors from "@fastify/cors"; +import supertokens from "supertokens-node"; +import { plugin } from "supertokens-node/framework/fastify"; +import formDataPlugin from "@fastify/formbody"; + +import fastifyImport from "fastify"; + +let fastify = fastifyImport(); + +// ...other middlewares +// highlight-start +fastify.register(cors, { + origin: "^{form_websiteDomain}", + allowedHeaders: ['Content-Type', ...supertokens.getAllCORSHeaders()], + credentials: true, +}); +// highlight-end + +(async () => { + // highlight-next-line + await fastify.register(formDataPlugin); + // highlight-next-line + await fastify.register(plugin); + + await fastify.listen(8000); +})(); + +// ...your API routes +``` + + + + +:::important +Add the `middleware` BEFORE all your routes. +::: + +```tsx +import Koa from "koa"; +import cors from '@koa/cors'; +import supertokens from "supertokens-node"; +import { middleware } from "supertokens-node/framework/koa"; + +let app = new Koa(); + +app.use(cors({ + // highlight-start + origin: "^{form_websiteDomain}", + allowHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], + credentials: true, + // highlight-end +})); + +// highlight-next-line +app.use(middleware()); + +// ...your API routes +``` + + + + +:::important +Add the `middleware` BEFORE all your routes. +::: + +```tsx +import { RestApplication } from "@loopback/rest"; +import supertokens from "supertokens-node"; +import { middleware } from "supertokens-node/framework/loopback"; + +let app = new RestApplication({ + rest: { + cors: { + // highlight-start + origin: "^{form_websiteDomain}", + allowedHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], + credentials: true + // highlight-end + } + } +}); + +// highlight-next-line +app.middleware(middleware); + +// ...your API routes +``` + + + + + + + + +Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. + +```go +import ( + "net/http" + "strings" + + "github.com/supertokens/supertokens-golang/supertokens" +) + +func main() { + // SuperTokens init... + + //highlight-start + http.ListenAndServe("SERVER ADDRESS", corsMiddleware( + supertokens.Middleware(http.HandlerFunc(func(rw http.ResponseWriter, + //highlight-end + r *http.Request) { + // TODO: Handle your APIs.. + + })))) +} + +func corsMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(response http.ResponseWriter, r *http.Request) { + response.Header().Set("Access-Control-Allow-Origin", "^{form_websiteDomain}") + response.Header().Set("Access-Control-Allow-Credentials", "true") + if r.Method == "OPTIONS" { + // we add content-type + other headers used by SuperTokens + response.Header().Set("Access-Control-Allow-Headers", + strings.Join(append([]string{"Content-Type"}, + //highlight-start + supertokens.GetAllCORSHeaders()...), ",")) + //highlight-end + response.Header().Set("Access-Control-Allow-Methods", "*") + response.Write([]byte("")) + } else { + next.ServeHTTP(response, r) + } + }) +} +``` + + + + +Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. + +```go +import ( + "net/http" + + "github.com/gin-contrib/cors" + "github.com/gin-gonic/gin" + "github.com/supertokens/supertokens-golang/supertokens" +) + +func main() { + // SuperTokens init... + + router := gin.New() + + // CORS + router.Use(cors.New(cors.Config{ + AllowOrigins: []string{"^{form_websiteDomain}"}, + AllowMethods: []string{"GET", "POST", "DELETE", "PUT", "OPTIONS"}, + AllowHeaders: append([]string{"content-type"}, + // highlight-next-line + supertokens.GetAllCORSHeaders()...), + AllowCredentials: true, + })) + + // Adding the SuperTokens middleware + // highlight-start + router.Use(func(c *gin.Context) { + supertokens.Middleware(http.HandlerFunc( + func(rw http.ResponseWriter, r *http.Request) { + c.Next() + })).ServeHTTP(c.Writer, c.Request) + // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly + c.Abort() + }) + // highlight-end + + // Add APIs and start server +} +``` + + + + +Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. + +```go +import ( + "github.com/go-chi/chi" + "github.com/go-chi/cors" + "github.com/supertokens/supertokens-golang/supertokens" +) + +func main() { + // SuperTokens init... + + r := chi.NewRouter() + + // CORS + r.Use(cors.Handler(cors.Options{ + AllowedOrigins: []string{"^{form_websiteDomain}"}, + AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, + AllowedHeaders: append([]string{"Content-Type"}, + //highlight-next-line + supertokens.GetAllCORSHeaders()...), + AllowCredentials: true, + })) + + // SuperTokens Middleware + //highlight-next-line + r.Use(supertokens.Middleware) + + // Add APIs and start server +} +``` + + + +Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. + +```go +import ( + "net/http" + + "github.com/gorilla/handlers" + "github.com/gorilla/mux" + "github.com/supertokens/supertokens-golang/supertokens" +) + +func main() { + // SuperTokens init... + + // TODO: Add APIs + + router := mux.NewRouter() + + // Adding handlers.CORS(options)(supertokens.Middleware(router))) + //highlight-start + http.ListenAndServe("SERVER ADDRESS", handlers.CORS( + handlers.AllowedHeaders(append([]string{"Content-Type"}, + supertokens.GetAllCORSHeaders()...)), + handlers.AllowedMethods([]string{"GET", "POST", "PUT", "HEAD", "OPTIONS"}), + handlers.AllowedOrigins([]string{"^{form_websiteDomain}"}), + handlers.AllowCredentials(), + )(supertokens.Middleware(router))) + //highlight-end +} +``` + + + + + + + + + +Use the `Middleware` (**BEFORE all your routes**) and the `get_all_cors_headers()` functions as shown below. + +```python +from supertokens_python import get_all_cors_headers +from fastapi import FastAPI +from starlette.middleware.cors import CORSMiddleware +from supertokens_python.framework.fastapi import get_middleware + +app = FastAPI() +# highlight-next-line +app.add_middleware(get_middleware()) + +# TODO: Add APIs + +app.add_middleware( + CORSMiddleware, + allow_origins=[ + "^{form_websiteDomain}" + ], + allow_credentials=True, + allow_methods=["GET", "PUT", "POST", "DELETE", "OPTIONS", "PATCH"], + # highlight-next-line + allow_headers=["Content-Type"] + get_all_cors_headers(), +) + +# TODO: start server +``` + + + + +- Use the `Middleware` (**BEFORE all your routes and after calling init function**) and the `get_all_cors_headers()` functions as shown below. +- Add a route to catch all paths and return a 404. This is needed because if we don't add this, then OPTIONS request for the APIs exposed by the `Middleware` will return a `404`. + +```python +from supertokens_python import get_all_cors_headers +from flask import Flask, abort +from flask_cors import CORS # type: ignore +from supertokens_python.framework.flask import Middleware + +app = Flask(__name__) +# highlight-next-line +Middleware(app) + +# TODO: Add APIs + +CORS( + app=app, + origins=[ + "^{form_websiteDomain}" + ], + supports_credentials=True, + # highlight-next-line + allow_headers=["Content-Type"] + get_all_cors_headers(), +) + +# This is required since if this is not there, then OPTIONS requests for +# the APIs exposed by the supertokens' Middleware will return a 404 +# highlight-start +@app.route('/', defaults={'u_path': ''}) # type: ignore +@app.route('/') # type: ignore +def catch_all(u_path: str): + abort(404) +# highlight-end + +# TODO: start server +``` + + + + +Use the `Middleware` and the `get_all_cors_headers()` functions as shown below in your `settings.py`. + +```python +from supertokens_python import get_all_cors_headers +from typing import List +from corsheaders.defaults import default_headers + +CORS_ORIGIN_WHITELIST = [ + "^{form_websiteDomain}" +] + +CORS_ALLOW_CREDENTIALS = True + +CORS_ALLOWED_ORIGINS = [ + "^{form_websiteDomain}" +] + +CORS_ALLOW_HEADERS: List[str] = list(default_headers) + [ + "Content-Type" + # highlight-next-line +] + get_all_cors_headers() + +INSTALLED_APPS = [ + 'corsheaders', + 'supertokens_python' +] + +MIDDLEWARE = [ + 'corsheaders.middleware.CorsMiddleware', + ..., + # highlight-next-line + 'supertokens_python.framework.django.django_middleware.middleware', +] +# TODO: start server +``` + + + + + + + + + + + +You can review all the endpoints that are added through the use of **SuperTokens** by visiting the [API Specs](https://app.swaggerhub.com/apis/supertokens/FDI). + + + + + +## 4. Add the SuperTokens Error Handler + + + + + + + +Depending on the language and framework that you are using, you might need to add a custom error handler to your server. +The handler will catch all the authentication related errors and return proper HTTP responses that can be parsed by the frontend SDKs. + + + + + + +```tsx +import express, { Request, Response, NextFunction } from 'express'; +import { errorHandler } from "supertokens-node/framework/express"; + +let app = express(); + +// ...your API routes + +// highlight-start +// Add this AFTER all your routes +app.use(errorHandler()) +// highlight-end + +// your own error handler +app.use((err: unknown, req: Request, res: Response, next: NextFunction) => { /* ... */ }); +``` + + + +No additional `errorHandler` is required. + + + + +Add the `errorHandler` **Before all your routes and plugin registration** + +```tsx +import Fastify from "fastify"; +import { errorHandler } from "supertokens-node/framework/fastify"; + +let fastify = Fastify(); + +// highlight-next-line +fastify.setErrorHandler(errorHandler()); + +// ...your API routes +``` + + + +No additional `errorHandler` is required. + + + + +No additional `errorHandler` is required. + + + + + + +:::info +You can skip this step +::: + + + + +:::info +You can skip this step +::: + + + + + + +## 5. Secure Application Routes + +Now that your server can authenticate users, the final step that you need to take care of is to prevent unauthorized access to certain parts of the application. + + + + +For your APIs that require a user to be logged in, use the `verifySession` middleware. + + + + + +```tsx +import express from "express"; +import { verifySession } from "supertokens-node/recipe/session/framework/express"; +import { SessionRequest } from "supertokens-node/framework/express"; + +let app = express(); + +// highlight-start +app.post("/like-comment", verifySession(), (req: SessionRequest, res) => { + let userId = req.session!.getUserId(); + // highlight-end + //.... +}); +``` + + + + +```tsx +import Hapi from "@hapi/hapi"; +import { verifySession } from "supertokens-node/recipe/session/framework/hapi"; +import { SessionRequest } from "supertokens-node/framework/hapi"; + +let server = Hapi.server({ port: 8000 }); + +server.route({ + path: "/like-comment", + method: "post", + //highlight-start + options: { + pre: [ + { + method: verifySession() + }, + ], + }, + handler: async (req: SessionRequest, res) => { + let userId = req.session!.getUserId(); + //highlight-end + //... + } +}) +``` + + + +```tsx +import Fastify from "fastify"; +import { verifySession } from "supertokens-node/recipe/session/framework/fastify"; +import { SessionRequest } from "supertokens-node/framework/fastify"; + +let fastify = Fastify(); + +//highlight-start +fastify.post("/like-comment", { + preHandler: verifySession(), +}, (req: SessionRequest, res) => { + let userId = req.session!.getUserId(); + //highlight-end + //.... +}); +``` + + + + +```tsx +import KoaRouter from "koa-router"; +import { verifySession } from "supertokens-node/recipe/session/framework/koa"; +import { SessionContext } from "supertokens-node/framework/koa"; + +let router = new KoaRouter(); + +//highlight-start +router.post("/like-comment", verifySession(), (ctx: SessionContext, next) => { + let userId = ctx.session!.getUserId(); + //highlight-end + //.... +}); +``` + + + + +```tsx +import { inject, intercept } from "@loopback/core"; +import { RestBindings, MiddlewareContext, post, response } from "@loopback/rest"; +import { verifySession } from "supertokens-node/recipe/session/framework/loopback"; +import { SessionContext } from "supertokens-node/framework/loopback"; + +class LikeComment { + //highlight-start + constructor(@inject(RestBindings.Http.CONTEXT) private ctx: MiddlewareContext) { } + @post("/like-comment") + @intercept(verifySession()) + @response(200) + handler() { + let userId = (this.ctx as SessionContext).session!.getUserId(); + //highlight-end + //.... + } +} +``` + + + + + + +For your APIs that require a user to be logged in, use the `VerifySession` middleware. + + + + +```go +import ( + "fmt" + "net/http" + + "github.com/supertokens/supertokens-golang/recipe/session" +) + +func main() { + _ = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + // Wrap the API handler in session.VerifySession + session.VerifySession(nil, likeCommentAPI).ServeHTTP(rw, r) + }) +} + +func likeCommentAPI(w http.ResponseWriter, r *http.Request) { + // retrieve the session object as shown below + sessionContainer := session.GetSessionFromRequestContext(r.Context()) + + userID := sessionContainer.GetUserID() + + fmt.Println(userID) +} +``` + + + + +```go +import ( + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/supertokens/supertokens-golang/recipe/session" + "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" +) + +func main() { + router := gin.New() + + // Wrap the API handler in session.VerifySession + router.POST("/likecomment", verifySession(nil), likeCommentAPI) +} + +// This is a function that wraps the supertokens verification function +// to work the gin +func verifySession(options *sessmodels.VerifySessionOptions) gin.HandlerFunc { + return func(c *gin.Context) { + session.VerifySession(options, func(rw http.ResponseWriter, r *http.Request) { + c.Request = c.Request.WithContext(r.Context()) + c.Next() + })(c.Writer, c.Request) + // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly + c.Abort() + } +} + +func likeCommentAPI(c *gin.Context) { + // retrieve the session object as shown below + sessionContainer := session.GetSessionFromRequestContext(c.Request.Context()) + + userID := sessionContainer.GetUserID() + + fmt.Println(userID) +} +``` + + + + +```go +import ( + "fmt" + "net/http" + + "github.com/go-chi/chi" + "github.com/supertokens/supertokens-golang/recipe/session" +) + +func main() { + r := chi.NewRouter() + + // Wrap the API handler in session.VerifySession + r.Post("/likecomment", session.VerifySession(nil, likeCommentAPI)) +} + +func likeCommentAPI(w http.ResponseWriter, r *http.Request) { + // retrieve the session object as shown below + sessionContainer := session.GetSessionFromRequestContext(r.Context()) + + userID := sessionContainer.GetUserID() + + fmt.Println(userID) +} +``` + + + + +```go +import ( + "fmt" + "net/http" + + "github.com/gorilla/mux" + "github.com/supertokens/supertokens-golang/recipe/session" +) + +func main() { + router := mux.NewRouter() + + // Wrap the API handler in session.VerifySession + router.HandleFunc("/likecomment", session.VerifySession(nil, likeCommentAPI)).Methods(http.MethodPost) +} + +func likeCommentAPI(w http.ResponseWriter, r *http.Request) { + // retrieve the session object as shown below + sessionContainer := session.GetSessionFromRequestContext(r.Context()) + + userID := sessionContainer.GetUserID() + + fmt.Println(userID) +} +``` + + + + + + +For your APIs that require a user to be logged in, use the `verify_session` middleware. + + + + +```python +from supertokens_python.recipe.session.framework.fastapi import verify_session +from supertokens_python.recipe.session import SessionContainer +from fastapi import Depends + +# highlight-start +@app.post('/like_comment') # type: ignore +async def like_comment(session: SessionContainer = Depends(verify_session())): + user_id = session.get_user_id() + # highlight-end + + print(user_id) +``` + + + + +```python +from supertokens_python.recipe.session.framework.flask import verify_session +from supertokens_python.recipe.session import SessionContainer +from flask import g + +# highlight-start +@app.route('/update-jwt', methods=['POST']) # type: ignore +@verify_session() +def like_comment(): + session: SessionContainer = g.supertokens # type: ignore + + user_id = session.get_user_id() + # highlight-end + + print(user_id) +``` + + + + +```python +from supertokens_python.recipe.session.framework.django.asyncio import verify_session +from django.http import HttpRequest +from supertokens_python.recipe.session import SessionContainer + +# highlight-start +@verify_session() +async def like_comment(request: HttpRequest): + session: SessionContainer = request.supertokens # type: ignore + + user_id = session.get_user_id() + # highlight-end + + print(user_id) +``` + + + + + + + +The middleware function returns a `401` to the frontend if a session doesn't exist, or if the access token has expired, in which case, our frontend SDK automatically refreshes the session. + +In case of successful session verification, you get access to a `session` object using which you can get the user's ID, or manipulate the session information. + +## 6. Test the Login Flow + +Now that you have configured both the frontend and the backend, you can return to the frontend login page. +From here follow these steps to confirm that your setup is working properly. +- Click on the **Sign up** button to create a new account. +- After you have created the account go to **Login** page and fill in your credentials. +- If you are greeted with the login screen you have completed the quickstart setup. + +:::success 🎉 Congratulations 🎉 + +You've successfully integrated **SuperTokens** with your existing application! + +Of course, there are additional things that you should add in order to provide a complete authentication experience. +We will talk about those things in the [next section](./next-steps). + +::: diff --git a/v2/emailpassword/quickstart/frontend-setup.mdx b/v2/emailpassword/quickstart/frontend-setup.mdx new file mode 100644 index 000000000..9aed0d223 --- /dev/null +++ b/v2/emailpassword/quickstart/frontend-setup.mdx @@ -0,0 +1,888 @@ +--- +id: frontend-setup +title: Frontend Setup +hide_title: true +show_ui_switcher: true +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" +import WebJsInjector from "/src/components/webJsInjector" + +import FrontendCustomUISDKInstall from '../../community/reusableMD/custom-ui/frontent-custom-ui-sdk-install.mdx' +import FrontendCustomUISessionTokens from '../../community/reusableMD/custom-ui/frontend-custom-ui-session-tokens.mdx' +import FrontendCustomUISessionManagement from '../../community/reusableMD/custom-ui/frontend-custom-ui-session-management.mdx' +import FrontendCustomUISignout from '../../community/reusableMD/custom-ui/frontend-custom-ui-signout.mdx' +import FrontendCustomUIEmailPasswordSingUp from '../../community/reusableMD/custom-ui/frontend-custom-ui-email-password-sign-up.mdx' +import FrontendCustomUIEmailPasswordSingIn from '../../community/reusableMD/custom-ui/frontend-custom-ui-email-password-sign-in.mdx' +import FrontendSDKInstall from "../../community/reusableMD/frontend-sdk-install.mdx" + + +import {CustomUILink, PrebuiltUILink, PreBuiltOrCustomUISwitcher, PreBuiltUIContent, CustomUIContent} from "/src/components/preBuiltOrCustomUISwitcher" +import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" +import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" +import PreBuiltUIInjector from "/src/components/prebuiltuiInjector" +import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; +import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" +import {Question, Answer}from "/src/components/question" +import RRDVersionSubTabs from "/src/components/tabs/RRDVersionSubTabs" +import AppInfoForm from "/src/components/appInfoForm" +import NodeJSFrameworkSubTabsServerless from "/src/components/tabs/NodeJSFrameworkSubTabsServerless" +import NpmVersionOrYarnSubTabs from "/src/components/tabs/NpmVersionOrYarnSubTabs" +import CoreInjector from "/src/components/coreInjector" +import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs" +import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" +import BackendSDKCasing from "/src/components/BackendSDKCasing" + + + +# Frontend Setup + +Start the setup by configuring your frontend application to use **SuperTokens** for authentication. + + + + + +This guide uses the **SuperTokens Pre Built UI** components. +If you want to create your own interface please check the **Custom UI** tutorial. + +## 1. Install the SDK + + + + + +Run the following command in your terminal to install the package. + + + + + +## 2. Initialize the SDK + + + + + + + + + + + +In your main application file call the `SuperTokens.init` function to initialize the SDK. +The `init` call includes the [main configuration details](../appinfo), as well as the **recipes** that you will be using in your setup. +After that you will have to wrap the application with the `SuperTokensWrapper` component. +This will provide authentication context for the rest of the UI tree. + + + +```tsx +import React from 'react'; + +// highlight-start +import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; +import EmailPassword from "supertokens-auth-react/recipe/emailpassword"; +import Session from "supertokens-auth-react/recipe/session"; + +SuperTokens.init({ + appInfo: { + // learn more about this on https://supertokens.com/docs/emailpassword/appinfo + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + websiteDomain: "^{form_websiteDomain}", + apiBasePath: "^{form_apiBasePath}", + websiteBasePath: "^{form_websiteBasePath}", + }, + recipeList: [ + EmailPassword.init(), + Session.init() + ] +}); +// highlight-end + + +/* Your App */ +class App extends React.Component { + render() { + return ( + // highlight-next-line + + {/*Your app components*/} + // highlight-next-line + + ); + } +} +``` + + + + + + + + + + + +Before we initialize the `supertokens-web-js` SDK let's see how we will use it in our Angular app. + +**Architecture** + +- The `supertokens-web-js` SDK is responsible for session management and providing helper functions to check if a session exists, or validate the access token claims on the frontend (for example, to check for user roles before showing some UI). We will initialise this SDK on the root of your Angular app, so that all pages in your app can use it. +- You will have to create a `^{form_websiteBasePath}*` route in the Angular app which will render our Pre Built UI. which will also need to be initialised, but only on that route. + +**Creating the `^{form_websiteBasePath}` route** + +- Use the Angular CLI to generate a new route + + ```bash + ng generate module auth --route auth --module app.module + ``` + +- Add the following code to your `auth` angular component + + ```tsx title="/app/auth/auth.component.ts" + import {init as supertokensUIInit} from "supertokens-auth-react-script"; + import supertokensUIEmailPassword from "supertokens-auth-react-script/recipe/emailpassword"; + import supertokensUISession from "supertokens-auth-react-script/recipe/session"; + 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: [ + supertokensUIEmailPassword.init(), + supertokensUISession.init(), + ], + }); + } + this.renderer.appendChild(this.document.body, script); + } + } + ``` + - In the `loadScript` function, we provide the SuperTokens config for the UI. We add the emailpassword and session recipe. + +- Initialize the `supertokens-web-js` SDK in your angular app's root component. This will provide session management across your entire application. + + ```tsx title="/app/app.component.ts " + import SuperTokens from "supertokens-web-js"; + import Session from "supertokens-web-js/recipe/session"; + + SuperTokens.init({ + appInfo: { + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}", + }, + recipeList: [ + Session.init(), + ], + }); + ``` + +
+ +
+ +
+ + + + + + + +Before we initialize the `supertokens-web-js` SDK let's see how we will use it in our Vue app + +**Architecture** + +- The `supertokens-web-js` SDK is responsible for session management and providing helper functions to check if a session exists, or validate the access token claims on the frontend (for example, to check for user roles before showing some UI). We will initialise this SDK on the root of your Vue app, so that all pages in your app can use it. +- We will create a `^{form_websiteBasePath}*` route in the Vue app which will render our pre built UI which will also need to be initialised, but only on that route. + +**Creating the `^{form_websiteBasePath}` route** + +- Create a new file `AuthView.vue`, this Vue component will be used to render the auth component: + ```tsx + import {init as supertokensUIInit} from "supertokens-auth-react-script"; + import supertokensUIEmailPassword from "supertokens-auth-react-script/recipe/emailpassword"; + import supertokensUISession from "supertokens-auth-react-script/recipe/session"; + + + + ``` + - In the `loadScript` function, we provide the SuperTokens config for the UI. We add the emailpassword and session recipe. + +- Initialize the `supertokens-web-js` SDK in your Vue app's `main.ts` file. This will provide session management across your entire application. + + ```tsx title="/main.ts " + // @ts-ignore + import { createApp } from "vue"; + import SuperTokens from "supertokens-web-js"; + import Session from "supertokens-web-js/recipe/session"; + // @ts-ignore + import App from "./App.vue"; + // @ts-ignore + import router from "./router"; + + SuperTokens.init({ + appInfo: { + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}", + }, + recipeList: [ + Session.init(), + ], + }); + + const app = createApp(App); + + app.use(router); + + app.mount("#app"); + + ``` + + + + + + + + + +:::important +SuperTokens does not support pre-built UI for mobile frameworks. Please toggle the **Custom UI** option from the *Table of Contents* section. +::: + + + +
+ +## 3. Configure Routing + + + + + + + + +In order for the **Pre Built UI** to be rendered inside your application, will will have to specify which routes will show the authentication components. +The **React SDK** uses [**React Router**](https://reactrouter.com/en/main) under the hood to achieve this. +Based on whether you already use this package or not in your project, there are two different ways of configuring the routes. + + + + + + + + + +Call the `getSuperTokensRoutesForReactRouterDom` method from within any `react-router-dom` `Routes` component. + +```tsx +import React from 'react'; +import { + BrowserRouter, + Routes, + Route, + Link +} from "react-router-dom"; + +// highlight-next-line +import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; +import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui"; +^{prebuiltuiimport} +import * as reactRouterDom from "react-router-dom"; + +class App extends React.Component { + render() { + return ( + + + + {/*This renders the login UI on the ^{form_websiteBasePath} route*/} + // highlight-next-line + {getSuperTokensRoutesForReactRouterDom(reactRouterDom, [^{recipePreBuiltUINameCapitalLetters}])} + {/*Your app routes*/} + + + + ); + } +} +``` + + + + + +Call the `getSuperTokensRoutesForReactRouterDom` method from within any `react-router-dom` `Switch` component. + +```tsx +import React from 'react'; +import { + BrowserRouter, + Switch, + Route, + Link +} from "react-router-dom5"; + +// highlight-next-line +import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; +import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui"; +^{prebuiltuiimport} +import * as reactRouterDom from "react-router-dom"; + + +class App extends React.Component { + render() { + return ( + + + + {/*This renders the login UI on the ^{form_websiteBasePath} route*/} + // highlight-next-line + {getSuperTokensRoutesForReactRouterDom(reactRouterDom, [^{recipePreBuiltUINameCapitalLetters}])} + {/*Your app routes*/} + + + + ); + } +} +``` + + + + + + + + +Add the highlighted code snippet to your root level `render` function. + +```tsx +import React from 'react'; +^{prebuiltuiimport} +import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; +import { canHandleRoute, getRoutingComponent } from "supertokens-auth-react/ui"; + +class App extends React.Component { + render() { + // highlight-start + if (canHandleRoute([^{recipePreBuiltUINameCapitalLetters}])) { + // This renders the login UI on the ^{form_websiteBasePath} route + return getRoutingComponent([^{recipePreBuiltUINameCapitalLetters}]) + } + // highlight-end + + return ( + {/*Your app*/} + ); + } + +} +``` + + + + + + + + + + +Update your angular router so that all auth related requests load the `auth` component + +```tsx title="/app/app-routing.module.ts" +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +const routes: Routes = [ + // highlight-start + { + path: "^{form_websiteBasePath_withoutForwardSlash}", + // @ts-ignore + loadChildren: () => import("./auth/auth.module").then((m) => m.AuthModule), + }, + + // @ts-ignore + { path: "**", loadChildren: () => import("./home/home.module").then((m) => m.HomeModule) }, + // highlight-end +]; + +@NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule], +}) +export class AppRoutingModule {} +``` + + + + + + + + +Update your Vue router so that all auth related requests load the `AuthView` component + +```tsx title="/router/index.ts" +// @ts-ignore +import { createRouter, createWebHistory } from "vue-router"; +// @ts-ignore +import HomeView from "../views/HomeView.vue"; +// @ts-ignore +import AuthView from "../views/AuthView.vue"; + +const router = createRouter({ + // @ts-ignore + history: createWebHistory(import.meta.env.BASE_URL), + routes: [ + { + path: "/", + name: "home", + component: HomeView, + }, + { + path: "^{form_websiteBasePath}/:pathMatch(.*)*", + name: "auth", + component: AuthView, + }, + ], +}); + +export default router; +``` + + + + + + + +:::important +SuperTokens does not support pre-built UI for mobile frameworks. Please toggle the **Custom UI** option from the *Table of Contents* section. +::: + + + + + + + +## 4. Handle Session Tokens + + + + + +This part is handled automatically by the **Frontend SDK**. +You don not need to do anything. +The step serves more as a way for us to tell you how is this handled under the hood. + +After you call the `init` function, the **SDK** will add interceptors to both `fetch` and `XHR`, XMLHTTPRequest. The latter is used by the `axios` library. +The interceptors save the session tokens that are generated from the authentication flow. +Those tokens are then added to requests initialized by your frontend app which target the backend API. +By default, the tokens are stored through session cookies but you can also switch to [header based authentication](../common-customizations/sessions/token-transfer-method). + + + +## 5. Secure Application Routes + +In order to prevent unauthorized access to ceratain parts of your frontend application you can use our utilities. +Follow the code samples below to understand how to do this. + + + + + +You can wrap your components with the `` react component. This will ensure that your component renders only if the user is logged in. If they are not logged in, the user will be redirected to the login page. + +```tsx +import React from "react"; +import { + BrowserRouter, + Routes, + Route, +} from "react-router-dom"; +// highlight-next-line +import { SessionAuth } from "supertokens-auth-react/recipe/session"; +// @ts-ignore +import MyDashboardComponent from "./dashboard"; + +class App extends React.Component { + render() { + return ( + + + + {/*Components that require to be protected by authentication*/} + + + // highlight-end + } /> + + + ); + } +} +``` + + + + + +You can use the `doesSessionExist` function to check if a session exists in all your routes. + +```tsx +import Session from 'supertokens-web-js/recipe/session'; + +async function doesSessionExist() { + if (await Session.doesSessionExist()) { + // user is logged in + } else { + // user has not logged in yet + } +} +``` + + + + + +## 6. View the login UI + + + +You can check the login UI by visiting the `^{form_websiteBasePath}` route, in your frontend application. +To review all the components of our pre-built UI please follow [this link](https://master--6571be2867f75556541fde98.chromatic.com/?path=/story/auth-page--playground). + + + + + +
+ + + +This guide shows you how to create your own UI on top of the **SuperTokens SDK**. +If you want to use our **Pre Built Components** please check the following tutorial. + +## 1. Install the SDK + + + +## 2. Initialize the SDK + +Call the SDK init function at the start of your application. +The invocation includes the [main configuration details](../appinfo), as well as the **recipes** that you will be using in your setup. + + + + + + + + + +```tsx +import SuperTokens from 'supertokens-web-js'; +import Session from 'supertokens-web-js/recipe/session'; +import ^{recipeNameCapitalLetters} from 'supertokens-web-js/recipe/^{codeImportRecipeName}' + +SuperTokens.init({ + appInfo: { + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}", + appName: "...", + }, + recipeList: [ + Session.init(), + ^{recipeNameCapitalLetters}.init(), + ], +}); +``` + + + + + + + + + +```tsx +import supertokens from "supertokens-web-js-script"; +import supertokensSession from "supertokens-web-js-script/recipe/session"; +import supertokens^{recipeNameCapitalLetters} from 'supertokens-web-js-script/recipe/^{codeImportRecipeName}' +supertokens.init({ + appInfo: { + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}", + appName: "...", + }, + recipeList: [ + supertokensSession.init(), + supertokens^{recipeNameCapitalLetters}.init(), + ], +}); +``` + + + + + + + + + + + + + + + + + +```tsx +import SuperTokens from 'supertokens-react-native'; + +SuperTokens.init({ + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}", +}); +``` + + + + + + + + + +Add the `SuperTokens.init` function call at the start of your application. + +```kotlin +import android.app.Application +import com.supertokens.session.SuperTokens + +class MainApplication: Application() { + override fun onCreate() { + super.onCreate() + + SuperTokens.Builder(this, "^{form_apiDomain}") + .apiBasePath("^{form_apiBasePath}") + .build() + } +} +``` + + + + + + + + + + +```swift +import UIKit +import SuperTokensIOS + +fileprivate class ApplicationDelegate: UIResponder, UIApplicationDelegate { + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + do { + try SuperTokens.initialize( + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}" + ) + } catch SuperTokensError.initError(let message) { + // TODO: Handle initialization error + } catch { + // Some other error + } + + return true + } + +} +``` + + + + + + + + + + +```dart +import 'package:supertokens_flutter/supertokens.dart'; + +void main() { + SuperTokens.init( + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}", + ); +} +``` + + + + + + + + + + + + +## 3. Add the Login UI + +The **Email/Password** flow involves two types of user interfaces. +One for registering and creating new users, the *Sign Up Form*. +And one for the actual authentication attempt, the *Sign In Form*. +If you are provisioning users from a different method you can skip over adding the sign up form. + +### 3.1 Add the Sign Up form + + + +### 3.2 Add the Sign In Form + + + +## 5. Handle Session Tokens + + + +## 6. Protect Frontend Routes + + + +## 7. Add a Sign Out Action + + + + + + + +
+ +:::success 🎉 Congratulations 🎉 + +Congratulations! You've successfully integrated your frontend app with SuperTokens. + +The [next section](./backend-setup) will guide you through setting up your backend and then you should be able to complete a login flow. + +::: diff --git a/v2/emailpassword/quickstart/introduction.mdx b/v2/emailpassword/quickstart/introduction.mdx new file mode 100644 index 000000000..753a279f6 --- /dev/null +++ b/v2/emailpassword/quickstart/introduction.mdx @@ -0,0 +1,85 @@ +--- +id: introduction +title: Introduction +hide_title: true +show_ui_switcher: false +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + +# Introduction + +## Overview + +This quickstart will guide you through how to set up a basic project that uses **SuperTokens** to authenticate users. +The tutorial shows an **Email/Password** login flow, rendered by either our **Prebuilt UI components** or by your own **Custom UI**. + + + + + +If you want to skip straight to an example application you can choose between: +- Checking our live [demo application](https://^{docsLinkRecipeName}.demo.supertokens.com/auth) +- Running a **SuperTokens** project from your local machine. You just have to use our CLI app and execute the following command: + +```bash +npx create-supertokens-app@latest --recipe=^{docsLinkRecipeName} +``` + + + + +## Before you start + + + + + +Before going into the actual tutorial, let's get a clear picture of how **SuperTokens** works and some of the terms that we will use throughout the documentation. + +### SuperTokens Core + +The main service that provides all the functionality is called the **SuperTokens Core**. SDKs communicate over an API with this service in order to +perform authentication related tasks. + +Unlike with other providers, the **SuperTokens Frontend SDK** never talks to the **Authentication Service** directly. +All the requests target your existing **Backend Service**. +From there, the **Backend SDKs** are used to expose new authentication routes. Those in turn communicate with the **SuperTokens Core**. + +You can check the following diagram for a high level overview of how the services will interact within an authentication setup that involves **SuperTokens**. + + + + Flowchart of architecture when using SuperTokens managed service + + + Flowchart of architecture when self-hosting SuperTokens + + + +:::info Edge Cases +- You can also host the **SuperTokens Core** yourself. In that case your backend will communicate with a service that exists inside your infrastructure. +- If you are using a backend for which we do not have an SDK, you will have to spin up an additional auth service in a language for which we do have a backend SDK (NodeJS, Python or Golang). +::: + + +### Recipes + +The functionalities that **SuperTokens** provides are bundled into objects that can be reffered to as **Recipes**. +Everything from *authentication methods* to *session and user management* can be included under this concept. +In the following sections, we will see how recipes get initialised and configured and how you can customise them to fit your use case. + + +Now that we have cleared all this out, we can move forward with the actual tutorial. +Go to the next page to see how to configure your [Frontend Application](./frontend-setup). + + + diff --git a/v2/emailpassword/quickstart/next-steps.mdx b/v2/emailpassword/quickstart/next-steps.mdx new file mode 100644 index 000000000..f138f8f7e --- /dev/null +++ b/v2/emailpassword/quickstart/next-steps.mdx @@ -0,0 +1,185 @@ +--- +id: next-steps +title: Next Steps +hide_title: true +show_ui_switcher: false +show_next_button: false +--- + +import Card from "/src/components/card/Card" +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +import PreBuiltUIInjector from "/src/components/prebuiltuiInjector" +import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; +import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" +import {Question, Answer}from "/src/components/question" +import RRDVersionSubTabs from "/src/components/tabs/RRDVersionSubTabs" +import AppInfoForm from "/src/components/appInfoForm" +import NodeJSFrameworkSubTabsServerless from "/src/components/tabs/NodeJSFrameworkSubTabsServerless" +import CoreInjector from "/src/components/coreInjector" +import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs" +import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" +import BackendSDKCasing from "/src/components/BackendSDKCasing" + + + +# Next Steps + + + + + +## Overview + +Now that you have completed the quickstart guide there are a few more things that you need to take care of on the road towards a production ready authentication experience. + +## Configure the Core Service + +If you have signed up and deployed a SuperTokens environment already, you can skip this step. +Otherwise, please follow these instructions to use the correct **SuperTokens Core** instance in your application. + +The steps show you how to connect to a **SuperTokens Managed Service Environment**. +If you want to self host the core instance please check the [following guide](../pre-built-ui/setup/core/with-docker). + +### 1. Sign up for a SuperTokens account + +Open this [page](https://supertokens.com/auth) in order to access the account creation page. +Select the account that you want to use and wait for the action to complete. + +### 2. Select the authentication method + +After you have created your account, you will be prompted with a form that will ask you to specify details about your configuration. +Select which authentication method that you want to use. + +Integration with SuperTokens SDKs + +### 3. Select the region where you want to deploy the core service# + +SuperTokens environments can be deployed in 3 different regions: `US East (N. Virginia)`, `Europe (Ireland)`, `Asia Pacific (Singapore)`. +In order to avoid any latency issues please select a region that is closest to where your services are hosted. + +### 4. Click the deploy button 🚀 + +Integration with SuperTokens SDKs + +Our internal service will deploy a separate environment based on your selection. + +After this process is complete, you will be directed to the dashboard page. Here you can view and edit information about your newly created environment. + +The main thing that we want to focus is the **Connecting to a development instance** section. +There you can see two different values, the `connectionURI` and the `apiKey`. +You will use these values in the next step. + +:::info + +The initial setup flow only configures a development environment. In order to use SuperTokens in production, you will have to click the Create Production Env button. + +::: + +### 5. Connect the Backend SDK with SuperTokens 🔌 + +Add the `connectionURI` and the `apiKey` shown on the dashboard to your code on the backend. + +SuperTokens managed service dashboard connectionURI and API key + + + + +```tsx +import supertokens from "supertokens-node"; + +supertokens.init({ + // highlight-start + supertokens: { + connectionURI: "", + apiKey: "" + }, + // highlight-end + appInfo: { + apiDomain: "...", + appName: "...", + websiteDomain: "..." + }, + recipeList: [] +}); +``` + + + + +```go +import "github.com/supertokens/supertokens-golang/supertokens" + +func main() { + supertokens.Init(supertokens.TypeInput{ + // highlight-start + Supertokens: &supertokens.ConnectionInfo{ + ConnectionURI: "", + APIKey: "", + }, + // highlight-end + }) +} + +``` + + + + +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig + +init( + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + # highlight-start + supertokens_config=SupertokensConfig( + connection_uri='', + api_key='' + ), + # highlight-end + framework='...', # type: ignore + recipe_list=[ + #... + ] +) +``` + + + + + + + +## Customize Your Authentication Flow + +After you have connected the Backend SDK to a specific **SuperTokens Core Service**, you can go check the rest of the documentation. +There are several sections that show you how to customize the authentication experience to fit your specific needs. +Some of the most common subjects are: + +- [**Add Email Verification**](../common-customizations/email-verification/about) +- [**Add a Custom Redirect Action**](../pre-built-ui/auth-redirection) +- [**Use Custom Session Management**](../common-customizations/sessions/session-verification-in-api/get-session) +- [**Share Sessions Across Subdomains**](../common-customizations/sessions/share-sessions-across-sub-domains) +- [**Post Sign In Actions**](../common-customizations/handling-signin-success) +- [**Password Hashing**](../common-customizations/password-hashing/about) + + +## Explore Additional Features + +You can also review the additional features that **SuperTokens** exposes. +Those can help you extend the authentication implementation on different levels. + +- [**Self Host SuperTokens Core**](../pre-built-ui/setup/core/with-docker) +- [**Migration Guide**](../migration/about) +- [**Multi Factor Authentication**](../../mfa/introduction) +- [**Manage Users through the User Management Dashboard**](../pre-built-ui/setup/user-management-dashboard/setup) +- [**Multi Tenancy**](../../multitenancy/introduction) +- [**User Roles and Permissions**](../user-roles/initialisation) + + + + + + + diff --git a/v2/emailpassword/sidebars.js b/v2/emailpassword/sidebars.js index e97d7f7cb..238de82e0 100644 --- a/v2/emailpassword/sidebars.js +++ b/v2/emailpassword/sidebars.js @@ -1,159 +1,35 @@ module.exports = { sidebar: [ { + label: "quickstart", type: "category", - label: "Start Here", customProps: { highlightGroup: true, }, collapsed: false, items: [ - "introduction", { - type: "category", - label: "Quick setup with Pre built UI", - customProps: { - categoryIcon: "lightning", - }, - items: [ - { - type: "category", - label: "Setup", - collapsed: false, - items: [ - "pre-built-ui/setup/frontend", - "pre-built-ui/setup/backend", - { - type: "category", - label: "Step 3: Core", - items: [ - { - type: "category", - label: "Self hosted", - items: [ - "pre-built-ui/setup/core/with-docker", - "pre-built-ui/setup/core/without-docker", - { - type: "category", - label: "Database Setup", - items: [ - "pre-built-ui/setup/database-setup/mysql", - "pre-built-ui/setup/database-setup/postgresql", - "pre-built-ui/setup/database-setup/rename-database-tables", - ], - }, - ], - }, - "pre-built-ui/setup/core/saas-setup", - ], - }, - { - type: "category", - label: "Step 4: User management dashboard", - items: [ - "pre-built-ui/setup/user-management-dashboard/setup", - "pre-built-ui/setup/user-management-dashboard/users-listing-and-details", - "pre-built-ui/setup/user-management-dashboard/managing-user-roles-and-permissions", - { - type: "category", - label: "Tenant Management", - collapsed: true, - items: [ - "pre-built-ui/setup/user-management-dashboard/tenant-management/overview", - "pre-built-ui/setup/user-management-dashboard/tenant-management/details", - ], - }, - ], - }, - ], - }, - "pre-built-ui/handling-session-tokens", - "pre-built-ui/securing-routes", - "pre-built-ui/sign-out", - "pre-built-ui/auth-redirection", - "pre-built-ui/enable-email-verification", - "pre-built-ui/multitenant-login", - { - type: "category", - label: "Further Reading", - items: [ - "pre-built-ui/further-reading/email-password-login", - "pre-built-ui/further-reading/password-reset", - "pre-built-ui/further-reading/email-verification", - ], - }, - ], + id: "quickstart/introduction", + type: "doc", + label: "Introduction", }, { - type: "category", - label: "Using your own UI / Custom UI", - customProps: { - categoryIcon: "pencil", - }, - items: [ - { - type: "category", - label: "Initialisation", - collapsed: false, - items: [ - "custom-ui/init/frontend", - "custom-ui/init/backend", - { - type: "category", - label: "Step 3: Core", - items: [ - { - type: "category", - label: "Self hosted", - items: [ - "custom-ui/init/core/with-docker", - "custom-ui/init/core/without-docker", - { - type: "category", - label: "Database Setup", - items: [ - "custom-ui/init/database-setup/mysql", - "custom-ui/init/database-setup/postgresql", - "custom-ui/init/database-setup/rename-database-tables", - ], - }, - ], - }, - "custom-ui/init/core/saas-setup", - ], - }, - { - type: "category", - label: "Step 4: User management dashboard", - items: [ - "custom-ui/init/user-management-dashboard/setup", - "custom-ui/init/user-management-dashboard/users-listing-and-details", - "custom-ui/init/user-management-dashboard/managing-user-roles-and-permissions", - { - type: "category", - label: "Tenant Management", - collapsed: true, - items: [ - "custom-ui/init/user-management-dashboard/tenant-management/overview", - "custom-ui/init/user-management-dashboard/tenant-management/details", - ], - }, - ], - }, - ], - }, - "custom-ui/email-password-login", - "custom-ui/handling-session-tokens", - "custom-ui/forgot-password", - "custom-ui/securing-routes", - "custom-ui/sign-out", - "custom-ui/enable-email-verification", - "custom-ui/multitenant-login", - ], + id: "quickstart/frontend-setup", + type: "doc", + label: "Frontend Setup", + }, + { + id: "quickstart/backend-setup", + type: "doc", + label: "Backend Setup", + }, + { + id: "quickstart/next-steps", + type: "doc", + label: "Next Steps", }, ], }, - "user-object", { type: "category", label: "Integrations", @@ -373,6 +249,11 @@ module.exports = { "common-customizations/sessions/protecting-frontend-routes", "common-customizations/sessions/with-jwt/read-jwt", "common-customizations/sessions/ssr", + { + id: "pre-built-ui/handling-session-tokens", + type: "doc", + label: "Access Session Tokens", + }, { type: "category", label: "Reading / modifying session claims", @@ -465,6 +346,12 @@ module.exports = { "common-customizations/password-managers", ], }, + { id: "pre-built-ui/sign-out", type: "doc", label: "Add Sign Out" }, + { + id: "pre-built-ui/auth-redirection", + type: "doc", + label: "Add Redirect Actions", + }, "common-customizations/get-user-info", "common-customizations/user-pagination", "common-customizations/delete-user", @@ -552,6 +439,20 @@ module.exports = { ], }, "common-customizations/translations", + { + type: "category", + label: "Custom UI", + customProps: { + categoryIcon: "pencil", + }, + items: [ + { + type: "doc", + label: "Add a Forgot Password Flow", + id: "custom-ui/forgot-password", + }, + ], + }, { type: "category", label: "Changing base path", @@ -562,6 +463,28 @@ module.exports = { }, "common-customizations/multiple-clients", "common-customizations/userid-format", + { + id: "pre-built-ui/setup/core/saas-setup", + label: "Connecting to the SuperTokens Core Managed Service", + type: "doc", + }, + { + type: "category", + label: "Self Hosting SuperTokens Core", + items: [ + "pre-built-ui/setup/core/with-docker", + "pre-built-ui/setup/core/without-docker", + { + type: "category", + label: "Database Setup", + items: [ + "pre-built-ui/setup/database-setup/mysql", + "pre-built-ui/setup/database-setup/postgresql", + "pre-built-ui/setup/database-setup/rename-database-tables", + ], + }, + ], + }, { type: "category", label: @@ -691,6 +614,24 @@ module.exports = { "mfa", "multi-tenant", "attack-protection-suite", + { + type: "category", + label: "User Management dashboard", + items: [ + "pre-built-ui/setup/user-management-dashboard/setup", + "pre-built-ui/setup/user-management-dashboard/users-listing-and-details", + "pre-built-ui/setup/user-management-dashboard/managing-user-roles-and-permissions", + { + type: "category", + label: "Tenant Management", + collapsed: true, + items: [ + "pre-built-ui/setup/user-management-dashboard/tenant-management/overview", + "pre-built-ui/setup/user-management-dashboard/tenant-management/details", + ], + }, + ], + }, ], }, "scalability", @@ -731,11 +672,21 @@ module.exports = { label: "References", items: [ "architecture", + "user-object", "other-frameworks", "appinfo", "sdks", "apis", "compatibility-table", + { + type: "category", + label: "Prebuilt UI Components", + items: [ + "pre-built-ui/further-reading/email-password-login", + "pre-built-ui/further-reading/password-reset", + "pre-built-ui/further-reading/email-verification", + ], + }, ], }, ], diff --git a/v2/passwordless/common-customizations/email-verification/about.mdx b/v2/passwordless/common-customizations/email-verification/about.mdx index ceb8fd555..05daf80e1 100644 --- a/v2/passwordless/common-customizations/email-verification/about.mdx +++ b/v2/passwordless/common-customizations/email-verification/about.mdx @@ -5,11 +5,9 @@ hide_title: true show_ui_switcher: true --- - - - import {PreBuiltOrCustomUISwitcher, PreBuiltUIContent, CustomUIContent} from "/src/components/preBuiltOrCustomUISwitcher" import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" +import WebJsInjector from "/src/components/webJsInjector" import {Question, Answer}from "/src/components/question" import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs"; @@ -19,6 +17,11 @@ import AngularUIImplementation from "/src/components/reusableSnippets/angularUII import VueUIImplementation from "/src/components/reusableSnippets/vueUIImplementation" import { OAuthEmailVerificationDisclaimer }from "/src/components/OAuthDisclaimer" +import CustomAdmonition from "/src/components/customAdmonition" +import NodeJSFrameworkSubTabs from "/src/components/tabs/NodeJSFrameworkSubTabs"; +import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" +import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs"; + # Enable email verification @@ -275,7 +278,1198 @@ Additionally, note that SuperTokens does not send verification emails post user -See our guide [in the custom UI section](../../custom-ui/enable-email-verification). +:::important +For passwordless login with email, a user's email is automatically marked as verified when they login. Therefore, the only time this flow would be triggered is if a user changes their email during a session. +::: + +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 + + + + +```tsx +import SuperTokens from "supertokens-node"; +import EmailVerification from "supertokens-node/recipe/emailverification"; +import Session from "supertokens-node/recipe/session"; + +SuperTokens.init({ + appInfo: { + apiDomain: "...", + appName: "...", + websiteDomain: "...", + }, + recipeList: [ + // highlight-start + EmailVerification.init({ + mode: "REQUIRED", // or "OPTIONAL" + }), + // highlight-end + Session.init(), + ], +}); +``` + + + + +```go +import ( + "github.com/supertokens/supertokens-golang/recipe/emailverification" + "github.com/supertokens/supertokens-golang/recipe/emailverification/evmodels" + "github.com/supertokens/supertokens-golang/recipe/session" + "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" + "github.com/supertokens/supertokens-golang/supertokens" +) + +func main() { + supertokens.Init(supertokens.TypeInput{ + RecipeList: []supertokens.Recipe{ + // highlight-start + emailverification.Init(evmodels.TypeInput{ + Mode: evmodels.ModeRequired, // or evmodels.ModeOptional + }), + // highlight-end + session.Init(&sessmodels.TypeInput{}), + }, + }) +} +``` + + + + +```python +from supertokens_python import init, InputAppInfo +from supertokens_python.recipe import session +from supertokens_python.recipe import emailverification + +init( + app_info=InputAppInfo( + api_domain="...", app_name="...", website_domain="..."), + framework='...', # type: ignore + recipe_list=[ + # highlight-start + emailverification.init(mode='REQUIRED'), # or 'OPTIONAL' + # highlight-end + session.init() + ] +) +``` + + + + + + +## Step 2: Frontend setup + + + + + + + +```tsx +import SuperTokens from "supertokens-web-js"; +import EmailVerification from "supertokens-web-js/recipe/emailverification"; +import Session from "supertokens-web-js/recipe/session"; + +SuperTokens.init({ + appInfo: { + apiDomain: "...", + appName: "...", + }, + recipeList: [ + // highlight-start + EmailVerification.init(), + Session.init(), + ], +}); +``` + + + + +Add the following ` +``` + + + +Then call the `supertokensEmailVerification.init` function as shown below + +```tsx +import supertokens from "supertokens-web-js-script"; +import supertokensEmailVerification from "supertokens-web-js-script/recipe/emailverification"; +import supertokensSession from "supertokens-web-js-script/recipe/session"; +supertokens.init({ + appInfo: { + apiDomain: "...", + appName: "...", + }, + recipeList: [ + // highlight-start + supertokensEmailVerification.init(), + supertokensSession.init(), + ], +}); +``` + + + + + + + + +:::success +No specific action required here. +::: + + + + + +## Step 3: Checking if the user's email is verified in your APIs + +

If using REQUIRED mode

+ +On the backend, when you initialize the email verification recipe in this mode, the `verifySession` middleware automatically checks if the user's email is verified based on the contents of the session's payload. If the email is not verified, the `verifySession` middleware will return a `403` status code to the client. + +

If using OPTIONAL mode

+ +In this mode, you need to check if the email is verified yourself in the APIs in which you want this constraint. The verification status should already be in the session's payload. + + + + + + +```tsx +import { verifySession } from "supertokens-node/recipe/session/framework/express"; +import express from "express"; +import { SessionRequest } from "supertokens-node/framework/express"; +import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; + +let app = express(); + +app.post( + "/update-blog", + verifySession({ + // highlight-next-line + overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], + }), + async (req: SessionRequest, res) => { + // All validator checks have passed and the user has a verified email address + } +); +``` + + + +```tsx +import Hapi from "@hapi/hapi"; +import { verifySession } from "supertokens-node/recipe/session/framework/hapi"; +import {SessionRequest} from "supertokens-node/framework/hapi"; +import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; + +let server = Hapi.server({ port: 8000 }); + +server.route({ + path: "/update-blog", + method: "post", + options: { + pre: [ + { + method: verifySession({ + // highlight-next-line + overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], + }), + }, + ], + }, + handler: async (req: SessionRequest, res) => { + // All validator checks have passed and the user has a verified email address + } +}) +``` + + + +```tsx +import Fastify from "fastify"; +import { verifySession } from "supertokens-node/recipe/session/framework/fastify"; +import { SessionRequest } from "supertokens-node/framework/fastify"; +import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; + +let fastify = Fastify(); + +fastify.post("/update-blog", { + preHandler: verifySession({ + // highlight-next-line + overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], + }), +}, async (req: SessionRequest, res) => { + // All validator checks have passed and the user has a verified email address +}); +``` + + + + +```tsx +import { verifySession } from "supertokens-node/recipe/session/framework/awsLambda"; +import { SessionEvent } from "supertokens-node/framework/awsLambda"; +import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; + +async function updateBlog(awsEvent: SessionEvent) { + // All validator checks have passed and the user has a verified email address +}; + +exports.handler = verifySession(updateBlog, { + // highlight-next-line + overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] +}); +``` + + + + +```tsx +import KoaRouter from "koa-router"; +import { verifySession } from "supertokens-node/recipe/session/framework/koa"; +import {SessionContext} from "supertokens-node/framework/koa"; +import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; + +let router = new KoaRouter(); + +router.post("/update-blog", verifySession({ + // highlight-next-line + overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] + }), async (ctx: SessionContext, next) => { + // All validator checks have passed and the user has a verified email address +}); +``` + + + + +```tsx +import { inject, intercept } from "@loopback/core"; +import { RestBindings, MiddlewareContext, post, response } from "@loopback/rest"; +import { verifySession } from "supertokens-node/recipe/session/framework/loopback"; +import Session from "supertokens-node/recipe/session"; +import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; + +class SetRole { + constructor(@inject(RestBindings.Http.CONTEXT) private ctx: MiddlewareContext) { } + @post("/update-blog") + @intercept(verifySession({ + // highlight-next-line + overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] + })) + @response(200) + async handler() { + // All validator checks have passed and the user has a verified email address + } +} +``` + + + + +```tsx +import { superTokensNextWrapper } from 'supertokens-node/nextjs' +import { verifySession } from "supertokens-node/recipe/session/framework/express"; +import { SessionRequest } from "supertokens-node/framework/express"; +import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; + +// highlight-start +export default async function setRole(req: SessionRequest, res: any) { + await superTokensNextWrapper( + async (next) => { + await verifySession({ + // highlight-next-line + overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] + })(req, res, next); + }, + req, + res + ) + // All validator checks have passed and the user has a verified email address +} +``` + + + + +```tsx +import SuperTokens from "supertokens-node"; +import { NextResponse, NextRequest } from "next/server"; +import { withSession } from "supertokens-node/nextjs"; +import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; +// @ts-ignore +import { backendConfig } from "@/app/config/backend"; + +SuperTokens.init(backendConfig()); + +export async function POST(request: NextRequest) { + return withSession(request, async (err, session) => { + if (err) { + return NextResponse.json(err, { status: 500 }); + } + // All validator checks have passed and the user has a verified email address + return NextResponse.json({ message: "Your email is verified!" }); + }, + { + // highlight-next-line + overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], + } + ); +} +``` + + + + +```tsx +import { Controller, Post, UseGuards, Request, Response, Session } from "@nestjs/common"; +import { SessionContainer, SessionClaimValidator } from "supertokens-node/recipe/session"; +// @ts-ignore +import { AuthGuard } from './auth/auth.guard'; +import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; + +@Controller() +export class ExampleController { + @Post('example') + @UseGuards(new AuthGuard({ + // highlight-next-line + overrideGlobalClaimValidators: async (globalValidators: SessionClaimValidator[]) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] + })) + async postExample(@Session() session: SessionContainer): Promise { + // All validator checks have passed and the user has a verified email address + return true; + } +} +``` + + + + + + + + +```go +import ( + "net/http" + + "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" + "github.com/supertokens/supertokens-golang/recipe/session" + "github.com/supertokens/supertokens-golang/recipe/session/claims" + "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" + "github.com/supertokens/supertokens-golang/supertokens" +) + +func main() { + _ = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + session.VerifySession(&sessmodels.VerifySessionOptions{ + // highlight-start + OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { + globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) + return globalClaimValidators, nil + }, + // highlight-end + }, exampleAPI).ServeHTTP(rw, r) + }) +} + +func exampleAPI(w http.ResponseWriter, r *http.Request) { + // TODO: session is verified and all validators have passed.. +} +``` + + + + +```go +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" + "github.com/supertokens/supertokens-golang/recipe/session" + "github.com/supertokens/supertokens-golang/recipe/session/claims" + "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" + "github.com/supertokens/supertokens-golang/supertokens" +) + +func main() { + router := gin.New() + + // Wrap the API handler in session.VerifySession + router.POST("/likecomment", verifySession(&sessmodels.VerifySessionOptions{ + // highlight-start + OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { + globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) + return globalClaimValidators, nil + }, + // highlight-end + }), exampleAPI) +} + +// This is a function that wraps the supertokens verification function +// to work the gin +func verifySession(options *sessmodels.VerifySessionOptions) gin.HandlerFunc { + return func(c *gin.Context) { + session.VerifySession(options, func(rw http.ResponseWriter, r *http.Request) { + c.Request = c.Request.WithContext(r.Context()) + c.Next() + })(c.Writer, c.Request) + // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly + c.Abort() + } +} + +func exampleAPI(c *gin.Context) { + // TODO: session is verified and all claim validators pass. +} +``` + + + + +```go +import ( + "net/http" + + "github.com/go-chi/chi" + "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" + "github.com/supertokens/supertokens-golang/recipe/session" + "github.com/supertokens/supertokens-golang/recipe/session/claims" + "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" + "github.com/supertokens/supertokens-golang/supertokens" +) + +func main() { + r := chi.NewRouter() + + // Wrap the API handler in session.VerifySession + r.Post("/likecomment", session.VerifySession(&sessmodels.VerifySessionOptions{ + // highlight-start + OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { + globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) + return globalClaimValidators, nil + }, + // highlight-end + }, exampleAPI)) +} + +func exampleAPI(w http.ResponseWriter, r *http.Request) { + // TODO: session is verified and all claim validators pass. +} +``` + + + + +```go +import ( + "net/http" + + "github.com/gorilla/mux" + "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" + "github.com/supertokens/supertokens-golang/recipe/session" + "github.com/supertokens/supertokens-golang/recipe/session/claims" + "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" + "github.com/supertokens/supertokens-golang/supertokens" +) + +func main() { + router := mux.NewRouter() + + // Wrap the API handler in session.VerifySession + router.HandleFunc("/likecomment", session.VerifySession(&sessmodels.VerifySessionOptions{ + // highlight-start + OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { + globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) + return globalClaimValidators, nil + }, + // highlight-end + }, exampleAPI)).Methods(http.MethodPost) +} + +func exampleAPI(w http.ResponseWriter, r *http.Request) { + // TODO: session is verified and all claim validators pass. +} +``` + + + + + + + + + +```python +from supertokens_python.recipe.session.framework.fastapi import verify_session +from supertokens_python.recipe.emailverification import EmailVerificationClaim +from supertokens_python.recipe.session import SessionContainer +from fastapi import Depends + +@app.post('/like_comment') # type: ignore +async def like_comment(session: SessionContainer = Depends( + verify_session( + # highlight-start + # We add the EmailVerificationClaim's is_verified validator + override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \ + [EmailVerificationClaim.validators.is_verified()] + # highlight-end + ) +)): + # All validator checks have passed and the user has a verified email address + pass +``` + + + + +```python +from supertokens_python.recipe.session.framework.flask import verify_session +from supertokens_python.recipe.emailverification import EmailVerificationClaim + +@app.route('/update-jwt', methods=['POST']) # type: ignore +@verify_session( + # highlight-start + # We add the EmailVerificationClaim's is_verified validator + override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \ + [EmailVerificationClaim.validators.is_verified()] + # highlight-end +) +def like_comment(): + # All validator checks have passed and the user has a verified email address + pass +``` + + + + +```python +from supertokens_python.recipe.session.framework.django.asyncio import verify_session +from django.http import HttpRequest +from supertokens_python.recipe.emailverification import EmailVerificationClaim + +@verify_session( + # highlight-start + # We add the EmailVerificationClaim's is_verified validator + override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \ + [EmailVerificationClaim.validators.is_verified()] + # highlight-end +) +async def like_comment(request: HttpRequest): + # All validator checks have passed and the user has a verified email address + pass +``` + + + + + + + +We add the SDK's `EmailVerificationClaim` validator to the `verifySession` middleware call as shown above, and that will only allow access if the email is verified, else it will return `403` to the frontend. + + +import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" + +## Step 4: Protecting frontend routes + + + + + + + +```tsx +import Session from "supertokens-web-js/recipe/session"; +import { EmailVerificationClaim } from "supertokens-web-js/recipe/emailverification"; + +async function shouldLoadRoute(): Promise { + if (await Session.doesSessionExist()) { + // highlight-start + let validationErrors = await Session.validateClaims(); + + if (validationErrors.length === 0) { + // user has verified their email address + return true; + } else { + for (const err of validationErrors) { + if (err.id === EmailVerificationClaim.id) { + // email is not verified + } + } + } + // highlight-end + } + // a session does not exist, or email is not verified + return false +} +``` + + + + +```tsx +import supertokensSession from "supertokens-web-js-script/recipe/session"; +import supertokensEmailVerification from "supertokens-web-js-script/recipe/emailverification"; +async function shouldLoadRoute(): Promise { + if (await supertokensSession.doesSessionExist()) { + // highlight-start + let validationErrors = await supertokensSession.validateClaims(); + + if (validationErrors.length === 0) { + // user has verified their email address + return true; + } else { + for (const err of validationErrors) { + if (err.id === supertokensEmailVerification.EmailVerificationClaim.id) { + // email is not verified + } + } + } + // highlight-end + } + // a session does not exist, or email is not verified + return false +} +``` + + + + +In your protected routes, you need to first check if a session exists, and then call the `Session.validateClaims` function as shown above. This function inspects the session's contents and runs claim validators on them. If a claim validator fails, it will be reflected in the `validationErrors` variable. The `EmailVerificationClaim` validator will be automatically checked by this function since you have initialized the email verification recipe. + +
Handling 403 responses on the frontend + + + +If your frontend queries a protected API on your backend and it fails with a 403, you can call the `validateClaims` function and loop through the errors to know which claim has failed: + +```tsx +import axios from "axios"; +import Session from "supertokens-web-js/recipe/session"; +import { EmailVerificationClaim, sendVerificationEmail } from "supertokens-web-js/recipe/emailverification"; + +async function callProtectedRoute() { + try { + let response = await axios.get("^{form_apiDomain}/protectedroute"); + } catch (error) { + // highlight-start + if (axios.isAxiosError(error) && error.response?.status === 403) { + let validationErrors = await Session.validateClaims(); + for (let err of validationErrors) { + if (err.id === EmailVerificationClaim.id) { + // email verification claim check failed + // We call the sendEmail function defined in the next section to send the verification email. + // await sendEmail(); + } else { + // some other claim check failed (from the global validators list) + } + } + // highlight-end + + } + } +} +``` + + + +
+ +
+ + + + + + + +```tsx +import SuperTokens from 'supertokens-react-native'; + +async function checkIfEmailIsVerified() { + if (await SuperTokens.doesSessionExist()) { + + // highlight-start + let isVerified: boolean = (await SuperTokens.getAccessTokenPayloadSecurely())["st-ev"].v; + + if (isVerified) { + // TODO.. + } else { + // TODO.. + } + // highlight-end + } +} +``` + + + + + +```kotlin +import android.app.Application +import com.supertokens.session.SuperTokens +import org.json.JSONObject + +class MainApplication: Application() { + fun checkIfEmailIsVerified() { + val accessTokenPayload: JSONObject = SuperTokens.getAccessTokenPayloadSecurely(this); + val isVerified: Boolean = (accessTokenPayload.get("st-ev") as JSONObject).get("v") as Boolean + if (isVerified) { + // TODO.. + } else { + // TODO.. + } + } +} +``` + + + + + +```swift +import UIKit +import SuperTokensIOS + +fileprivate class ViewController: UIViewController { + func checkIfEmailIsVerified() { + if let accessTokenPayload: [String: Any] = try? SuperTokens.getAccessTokenPayloadSecurely(), let emailVerificationObject: [String: Any] = accessTokenPayload["st-ev"] as? [String: Any], let isVerified: Bool = emailVerificationObject["v"] as? Bool { + if isVerified { + // Email is verified + } else { + // Email is not verified + } + } + } +} +``` + + + + + +```dart +import 'package:supertokens_flutter/supertokens.dart'; + +Future checkIfEmailIsVerified() async { + var accessTokenPayload = await SuperTokens.getAccessTokenPayloadSecurely(); + + if (accessTokenPayload.containsKey("st-ev")) { + Map emailVerificationObject = accessTokenPayload["st-ev"]; + + if (emailVerificationObject.containsKey("v")) { + bool isVerified = emailVerificationObject["v"]; + + if (isVerified) { + // Email is verified + } else { + // Email is not verified + } + } + } +} +``` + + + + + +
Handling 403 responses on the frontend + +If your frontend queries a protected API on your backend and it fails with a 403, you can check the value of the `st-ev` claim in the access token payload. If it is set to false you can send the verification email + +
+ +
+
+ +import AppInfoForm from "/src/components/appInfoForm" + +## Step 5: Sending the email verification email + +When the email verification validators fail, or post sign up, you want to redirect the user to a screen telling them that a verification email has been sent to them. On this screen, you should call the following API + + + + + + + +```tsx +import { sendVerificationEmail } from "supertokens-web-js/recipe/emailverification"; + +async function sendEmail() { + try { + let response = await sendVerificationEmail(); + if (response.status === "EMAIL_ALREADY_VERIFIED_ERROR") { + // This can happen if the info about email verification in the session was outdated. + // Redirect the user to the home page + window.location.assign("/home"); + } else { + // email was sent successfully. + window.alert("Please check your email and click the link in it") + } + } catch (err: any) { + if (err.isSuperTokensGeneralError === true) { + // this may be a custom error message sent from the API by you. + window.alert(err.message); + } else { + window.alert("Oops! Something went wrong."); + } + } +} +``` + + + + +```tsx +import supertokensEmailVerification from "supertokens-web-js/recipe/emailverification"; + +async function sendEmail() { + try { + let response = await supertokensEmailVerification.sendVerificationEmail(); + if (response.status === "EMAIL_ALREADY_VERIFIED_ERROR") { + // This can happen if the info about email verification in the session was outdated. + // Redirect the user to the home page + window.location.assign("/home"); + } else { + // email was sent successfully. + window.alert("Please check your email and click the link in it") + } + } catch (err: any) { + if (err.isSuperTokensGeneralError === true) { + // this may be a custom error message sent from the API by you. + window.alert(err.message); + } else { + window.alert("Oops! Something went wrong."); + } + } +} +``` + + + + + + + + + + +You should create a new screen on your app that asks the user to enter their email to which an email will be sent. This screen should ideally be linked to from the sign in form. + +Once the user has enters their email, you can call the following API to send an email verification email to that user: + +```bash +curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/user/email/verify/token' \ +--header 'Authorization: Bearer ...' +``` + + + +The response body from the API call has a `status` property in it: +- `status: "OK"`: An email was sent to the user successfully. +- `status: "EMAIL_ALREADY_VERIFIED_ERROR"`: This status can be returned if the info about email verification in the session was outdated. Redirect the user to the home page. +- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. + + + +You do not need to add the tenant ID to the path here because the backend fetches the tenantId of the user from the session token. + + + + + + +:::note +The API for sending an email verification email requires an active session. If you are using our frontend SDKs, then the session tokens should automatically get attached to the request. +::: + +### Changing the email verification link domain / path +By default, the email verification link will point to the `websiteDomain` that is configured on the backend, on the `/auth/verify-email` route (where `/auth` is the default value of `websiteBasePath`). + +If you want to change this to a different path, a different domain, or deep link it to your mobile / desktop app, then you can do so on the backend in the following way: + + + + +```tsx +import SuperTokens from "supertokens-node"; +import EmailVerification from "supertokens-node/recipe/emailverification"; + +SuperTokens.init({ + supertokens: { + connectionURI: "...", + }, + appInfo: { + apiDomain: "...", + appName: "...", + websiteDomain: "..." + }, + recipeList: [ + EmailVerification.init({ + mode: "OPTIONAL", + // highlight-start + emailDelivery: { + override: (originalImplementation) => { + return { + ...originalImplementation, + sendEmail(input) { + return originalImplementation.sendEmail({ + ...input, + emailVerifyLink: input.emailVerifyLink.replace( + // This is: `${websiteDomain}${websiteBasePath}/verify-email` + "http://localhost:3000/auth/verify-email", + "http://localhost:3000/your/path" + ) + } + ) + }, + } + } + } + // highlight-end + }) + ] +}); +``` + + + +```go +import ( + "strings" + + "github.com/supertokens/supertokens-golang/ingredients/emaildelivery" + "github.com/supertokens/supertokens-golang/recipe/emailverification" + "github.com/supertokens/supertokens-golang/recipe/emailverification/evmodels" + "github.com/supertokens/supertokens-golang/supertokens" +) + +func main() { + supertokens.Init(supertokens.TypeInput{ + RecipeList: []supertokens.Recipe{ + emailverification.Init(evmodels.TypeInput{ + Mode: evmodels.ModeOptional, + // highlight-start + EmailDelivery: &emaildelivery.TypeInput{ + Override: func(originalImplementation emaildelivery.EmailDeliveryInterface) emaildelivery.EmailDeliveryInterface { + ogSendEmail := *originalImplementation.SendEmail + + (*originalImplementation.SendEmail) = func(input emaildelivery.EmailType, userContext supertokens.UserContext) error { + // This is: `${websiteDomain}${websiteBasePath}/verify-email` + input.EmailVerification.EmailVerifyLink = strings.Replace( + input.EmailVerification.EmailVerifyLink, + "http://localhost:3000/auth/verify-email", + "http://localhost:3000/your/path", 1, + ) + return ogSendEmail(input, userContext) + } + return originalImplementation + }, + }, + // highlight-end + }), + }, + }) +} +``` + + + +```python +from supertokens_python import init, InputAppInfo +from supertokens_python.recipe import emailverification +from supertokens_python.ingredients.emaildelivery.types import EmailDeliveryConfig +from supertokens_python.recipe.emailverification.types import EmailDeliveryOverrideInput, EmailTemplateVars +from typing import Dict, Any + + +def custom_email_delivery(original_implementation: EmailDeliveryOverrideInput) -> EmailDeliveryOverrideInput: + original_send_email = original_implementation.send_email + + # highlight-start + async def send_email(template_vars: EmailTemplateVars, user_context: Dict[str, Any]) -> None: + + # This is: `${websiteDomain}${websiteBasePath}/verify-email` + template_vars.email_verify_link = template_vars.email_verify_link.replace( + "http://localhost:3000/auth/verify-email", "http://localhost:3000/your/path") + + return await original_send_email(template_vars, user_context) + # highlight-end + + original_implementation.send_email = send_email + return original_implementation + + +init( + app_info=InputAppInfo( + api_domain="...", app_name="...", website_domain="..."), + framework='...', # type: ignore + recipe_list=[ + emailverification.init( + mode="OPTIONAL", + # highlight-next-line + email_delivery=EmailDeliveryConfig(override=custom_email_delivery)) + ] +) +``` + + + + + + + +For a multi tenant setup, the input to the `sendEmail` function will also contain the `tenantId`. You can use this to determine the correct value to set for the websiteDomain in the generated link. + + + +## Step 6: Verifying the email post link clicked + +Once the user clicks the email verification link, and it opens your app, you can call the following function which will automatically extract the token and tenantId (if using a multi tenant setup) from the link and call the token verification API. + + + + + + + +```tsx +import { verifyEmail } from "supertokens-web-js/recipe/emailverification"; + +async function consumeVerificationCode() { + try { + let response = await verifyEmail(); + if (response.status === "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR") { + // This can happen if the verification code is expired or invalid. + // You should ask the user to retry + window.alert("Oops! Seems like the verification link expired. Please try again") + window.location.assign("/auth/verify-email") // back to the email sending screen. + } else { + // email was verified successfully. + window.location.assign("/home") + } + } catch (err: any) { + if (err.isSuperTokensGeneralError === true) { + // this may be a custom error message sent from the API by you. + window.alert(err.message); + } else { + window.alert("Oops! Something went wrong."); + } + } +} +``` + + + + +```tsx +import supertokensEmailVerification from "supertokens-web-js/recipe/emailverification"; + +async function consumeVerificationCode() { + try { + let response = await supertokensEmailVerification.verifyEmail(); + if (response.status === "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR") { + // This can happen if the verification code is expired or invalid. + // You should ask the user to retry + window.alert("Oops! Seems like the verification link expired. Please try again") + window.location.assign("/auth/verify-email") // back to the email sending screen. + } else { + // email was verified successfully. + window.location.assign("/home") + } + } catch (err: any) { + if (err.isSuperTokensGeneralError === true) { + // this may be a custom error message sent from the API by you. + window.alert(err.message); + } else { + window.alert("Oops! Something went wrong."); + } + } +} +``` + + + + + + + + + + +Once the user clicks the email verification link, and it opens as a deep link into your mobile app, you can extract the token from the link and call the verification API as shown below: + +```bash +curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/user/email/verify' \ +--header 'Content-Type: application/json; charset=utf-8' \ +--data-raw '{ + "method": "token", + "token": "ZTRiOTBjNz...jI5MTZlODkxw" +}' +``` + + + + +For a multi tenancy setup, the `` value can be fetched from `tenantId` query parameter from the email verification link. If it's not there in the link, you can use the value `"public"` (which is the default tenant). + + + + + + +The response body from the API call has a `status` property in it: +- `status: "OK"`: Email verification was successful. +- `status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR"`: This can happen if the verification code is expired or invalid. You should ask the user to retry. +- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. + + + + +:::caution +- This API doesn't require an active session to succeed. +- If you are calling the above API on page load, there is an edge case in which email clients might open the verification link in the email (for scanning purposes) and consume the token in the URL. This would lead to issues in which an attacker could sign up using someone else's email and end up with a veriifed status! + + To prevent this, on page load, you should check if a session exists, and if it does, only then call the above API. If a session does not exist, you should first show a button, which when clicked would call the above API (email clients won't automatically click on this button). The button text could be something like "Click here to verify your email". +::: + + +## See also + +- [Post email verification action](../common-customizations/email-verification/handling-email-verification-success) +- [Change email verification link's lifetime](../common-customizations/email-verification/changing-token-lifetime) +- [Customise email template or email delivery method](../email-delivery/about) +- [Manually changing email verification status for a user](../common-customizations/email-verification/changing-email-verification-status) +- [Generating email verification links manually](../common-customizations/email-verification/generate-link-manually) +- 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) in `REQUIRED` mode. +
diff --git a/v2/passwordless/common-customizations/multi-tenancy/new-tenant-config.mdx b/v2/passwordless/common-customizations/multi-tenancy/new-tenant-config.mdx index 0e4b072a3..d2faad651 100644 --- a/v2/passwordless/common-customizations/multi-tenancy/new-tenant-config.mdx +++ b/v2/passwordless/common-customizations/multi-tenancy/new-tenant-config.mdx @@ -26,196 +26,6 @@ The configuration mapped to each tenant contains information about which login m - - - - - -```tsx -import Multitenancy from "supertokens-node/recipe/multitenancy"; -import { FactorIds } from "supertokens-node/recipe/multifactorauth" - -async function createNewTenant() { - - // highlight-start - let resp = await Multitenancy.createOrUpdateTenant("customer1", { - firstFactors: [FactorIds.OTP_PHONE, FactorIds.OTP_EMAIL, FactorIds.LINK_PHONE, FactorIds.LINK_EMAIL] - }); - // highlight-end - - if (resp.createdNew) { - // new tenant was created - } else { - // existing tenant's config was modified. - } -} -``` - -In the example above, we have added all factor IDs related to the passwordless recipe. However, you can choose to use a subset of them. The factor IDs are described below: - -- Passwordless: - - With email OTP: `FactorIds.OTP_EMAIL` - - With SMS OTP: `FactorIds.OTP_PHONE` - - With email magic link: `FactorIds.LINK_EMAIL` - - With SMS magic link: `FactorIds.LINK_PHONE` - - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/multitenancy" - "github.com/supertokens/supertokens-golang/recipe/multitenancy/multitenancymodels" -) - -func main() { - tenantId := "customer1" - passwordlessEnabled := true - - // highlight-start - resp, err := multitenancy.CreateOrUpdateTenant(tenantId, multitenancymodels.TenantConfig{ - PasswordlessEnabled: &passwordlessEnabled, - }) - // highlight-end - - if err != nil { - // handle error - } - if resp.OK.CreatedNew { - // new tenant was created - } else { - // existing tenant's config was modified. - } -} -``` - - - - - - - -```python -from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate -from supertokens_python.recipe.multifactorauth.types import FactorIds - - -async def some_func(): - response = await create_or_update_tenant( - "customer1", - TenantConfigCreateOrUpdate( - first_factors=[ - FactorIds.OTP_PHONE, - FactorIds.OTP_EMAIL, - FactorIds.LINK_PHONE, - FactorIds.LINK_EMAIL, - ] - ), - ) - - if response.status != "OK": - print("Handle error") - elif response.created_new: - print("new tenant was created") - else: - print("existing tenant's config was modified.") -``` - - - - - -```python -from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate -from supertokens_python.recipe.multifactorauth.types import FactorIds - - -async def some_func(): - response = create_or_update_tenant( - "customer1", - TenantConfigCreateOrUpdate( - first_factors=[ - FactorIds.OTP_PHONE, - FactorIds.OTP_EMAIL, - FactorIds.LINK_PHONE, - FactorIds.LINK_EMAIL, - ] - ), - ) - - if response.status != "OK": - print("Handle error") - elif response.created_new: - print("new tenant was created") - else: - print("existing tenant's config was modified.") -``` - - - - - - - - - - - - - - -```bash -curl --location --request PUT '^{coreInjector_uri_without_quotes}/recipe/multitenancy/tenant/v2' \ ---header 'api-key: ^{coreInjector_api_key_without_quotes}' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "tenantId": "customer1", - "firstFactors": ["otp-email", "otp-phone", "link-email", "link-phone"] -}' -``` - -In the example above, we have added all factor IDs related to the passwordless recipe. However, you can choose to use a subset of them. The factor IDs are described below: - -- Passwordless: - - With email OTP: `otp-email` - - With SMS OTP: `otp-phone` - - With email magic link: `link-email` - - With SMS magic link: `link-phone` - - - - - - -```bash -curl --location --request PUT '^{coreInjector_uri_without_quotes}/recipe/multitenancy/tenant' \ ---header 'api-key: ^{coreInjector_api_key_without_quotes}' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "tenantId": "customer1", - "passwordlessEnabled": true -}' -``` - - - - - - - - - - - -Create Tenant - - - - - diff --git a/v2/passwordless/common-customizations/multi-tenancy/overview.mdx b/v2/passwordless/common-customizations/multi-tenancy/overview.mdx index 1ac22f36f..6078288b0 100644 --- a/v2/passwordless/common-customizations/multi-tenancy/overview.mdx +++ b/v2/passwordless/common-customizations/multi-tenancy/overview.mdx @@ -11,13 +11,6 @@ import TabItem from '@theme/TabItem'; - - - -# Multitenant login - -Multitenant login is a feature that lets you customize the login experience for each of your customers. For example, a customer `customer1` hosted on `customer1.yourdomain.com` can have login with email password (using this recipe), and another customer `customer2` hosted on `customer2.yourdomain.com` can have login with `Active Directory` and `Facebook` (using the thirdparty recipe). - ## Features diff --git a/v2/passwordless/common-customizations/sessions/protecting-frontend-routes.mdx b/v2/passwordless/common-customizations/sessions/protecting-frontend-routes.mdx index 9e5719247..7a0f1c394 100644 --- a/v2/passwordless/common-customizations/sessions/protecting-frontend-routes.mdx +++ b/v2/passwordless/common-customizations/sessions/protecting-frontend-routes.mdx @@ -291,133 +291,6 @@ async function shouldLoadRoute(): Promise { - - - - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```tsx -import Session from 'supertokens-web-js/recipe/session'; - -async function doesSessionExist() { - if (await Session.doesSessionExist()) { - // user is logged in - } else { - // user has not logged in yet - } -} -``` - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```tsx -import Session from 'supertokens-web-js-script/recipe/session'; -async function doesSessionExist() { - if (await Session.doesSessionExist()) { - // user is logged in - } else { - // user has not logged in yet - } -} -``` - - - - - - - - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```tsx -import SuperTokens from 'supertokens-react-native'; - -async function doesSessionExist() { - if (await SuperTokens.doesSessionExist()) { - // user is logged in - } else { - // user has not logged in yet - } -} -``` - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```kotlin -import android.app.Application -import com.supertokens.session.SuperTokens - -class MainApplication: Application() { - fun doesSessionExist() { - if (SuperTokens.doesSessionExist(this.applicationContext)) { - // user is logged in - } else { - // user has not logged in yet - } - } -} -``` - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```swift -import UIKit -import SuperTokensIOS - -fileprivate class ViewController: UIViewController { - func doesSessionExist() { - if SuperTokens.doesSessionExist() { - // User is logged in - } else { - // User is not logged in - } - } -} -``` - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```dart -import 'package:supertokens_flutter/supertokens.dart'; - -Future doesSessionExist() async { - return await SuperTokens.doesSessionExist(); -} -``` - - - - - - - - - ## Verifying the claims of a session diff --git a/v2/passwordless/custom-ui/enable-email-verification.mdx b/v2/passwordless/custom-ui/enable-email-verification.mdx index 7fca5151e..4f3ade3f4 100644 --- a/v2/passwordless/custom-ui/enable-email-verification.mdx +++ b/v2/passwordless/custom-ui/enable-email-verification.mdx @@ -4,1238 +4,8 @@ title: Enable email verification hide_title: true --- -# Enable email verification +import Redirector from '/src/components/Redirector'; -:::important -For passwordless login with email, a user's email is automatically marked as verified when they login. Therefore, the only time this flow would be triggered is if a user changes their email during a session. -::: - - - + -import CustomAdmonition from "/src/components/customAdmonition" -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"; - - -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 - - - - -```tsx -import SuperTokens from "supertokens-node"; -import EmailVerification from "supertokens-node/recipe/emailverification"; -import Session from "supertokens-node/recipe/session"; - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // highlight-start - EmailVerification.init({ - mode: "REQUIRED", // or "OPTIONAL" - }), - // highlight-end - Session.init(), - ], -}); -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/emailverification" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evmodels" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - supertokens.Init(supertokens.TypeInput{ - RecipeList: []supertokens.Recipe{ - // highlight-start - emailverification.Init(evmodels.TypeInput{ - Mode: evmodels.ModeRequired, // or evmodels.ModeOptional - }), - // highlight-end - session.Init(&sessmodels.TypeInput{}), - }, - }) -} -``` - - - - -```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import session -from supertokens_python.recipe import emailverification - -init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore - recipe_list=[ - # highlight-start - emailverification.init(mode='REQUIRED'), # or 'OPTIONAL' - # highlight-end - session.init() - ] -) -``` - - - - - - - - - - -import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" -import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" -import WebJsInjector from "/src/components/webJsInjector" - -## Step 2: Frontend setup - - - - - - - -```tsx -import SuperTokens from "supertokens-web-js"; -import EmailVerification from "supertokens-web-js/recipe/emailverification"; -import Session from "supertokens-web-js/recipe/session"; - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - }, - recipeList: [ - // highlight-start - EmailVerification.init(), - Session.init(), - ], -}); -``` - - - - -Add the following ` -``` - - - -Then call the `supertokensEmailVerification.init` function as shown below - -```tsx -import supertokens from "supertokens-web-js-script"; -import supertokensEmailVerification from "supertokens-web-js-script/recipe/emailverification"; -import supertokensSession from "supertokens-web-js-script/recipe/session"; -supertokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - }, - recipeList: [ - // highlight-start - supertokensEmailVerification.init(), - supertokensSession.init(), - ], -}); -``` - - - - - - - - -:::success -No specific action required here. -::: - - - - - - - - - - -## Step 3: Checking if the user's email is verified in your APIs - -

If using REQUIRED mode

- -On the backend, when you initialize the email verification recipe in this mode, the `verifySession` middleware automatically checks if the user's email is verified based on the contents of the session's payload. If the email is not verified, the `verifySession` middleware will return a `403` status code to the client. - -

If using OPTIONAL mode

- -In this mode, you need to check if the email is verified yourself in the APIs in which you want this constraint. The verification status should already be in the session's payload. - - - - - - -```tsx -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import express from "express"; -import { SessionRequest } from "supertokens-node/framework/express"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let app = express(); - -app.post( - "/update-blog", - verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - }), - async (req: SessionRequest, res) => { - // All validator checks have passed and the user has a verified email address - } -); -``` - - - -```tsx -import Hapi from "@hapi/hapi"; -import { verifySession } from "supertokens-node/recipe/session/framework/hapi"; -import {SessionRequest} from "supertokens-node/framework/hapi"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let server = Hapi.server({ port: 8000 }); - -server.route({ - path: "/update-blog", - method: "post", - options: { - pre: [ - { - method: verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - }), - }, - ], - }, - handler: async (req: SessionRequest, res) => { - // All validator checks have passed and the user has a verified email address - } -}) -``` - - - -```tsx -import Fastify from "fastify"; -import { verifySession } from "supertokens-node/recipe/session/framework/fastify"; -import { SessionRequest } from "supertokens-node/framework/fastify"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let fastify = Fastify(); - -fastify.post("/update-blog", { - preHandler: verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - }), -}, async (req: SessionRequest, res) => { - // All validator checks have passed and the user has a verified email address -}); -``` - - - - -```tsx -import { verifySession } from "supertokens-node/recipe/session/framework/awsLambda"; -import { SessionEvent } from "supertokens-node/framework/awsLambda"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -async function updateBlog(awsEvent: SessionEvent) { - // All validator checks have passed and the user has a verified email address -}; - -exports.handler = verifySession(updateBlog, { - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] -}); -``` - - - - -```tsx -import KoaRouter from "koa-router"; -import { verifySession } from "supertokens-node/recipe/session/framework/koa"; -import {SessionContext} from "supertokens-node/framework/koa"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let router = new KoaRouter(); - -router.post("/update-blog", verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - }), async (ctx: SessionContext, next) => { - // All validator checks have passed and the user has a verified email address -}); -``` - - - - -```tsx -import { inject, intercept } from "@loopback/core"; -import { RestBindings, MiddlewareContext, post, response } from "@loopback/rest"; -import { verifySession } from "supertokens-node/recipe/session/framework/loopback"; -import Session from "supertokens-node/recipe/session"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -class SetRole { - constructor(@inject(RestBindings.Http.CONTEXT) private ctx: MiddlewareContext) { } - @post("/update-blog") - @intercept(verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - })) - @response(200) - async handler() { - // All validator checks have passed and the user has a verified email address - } -} -``` - - - - -```tsx -import { superTokensNextWrapper } from 'supertokens-node/nextjs' -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import { SessionRequest } from "supertokens-node/framework/express"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -// highlight-start -export default async function setRole(req: SessionRequest, res: any) { - await superTokensNextWrapper( - async (next) => { - await verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - })(req, res, next); - }, - req, - res - ) - // All validator checks have passed and the user has a verified email address -} -``` - - - - -```tsx -import SuperTokens from "supertokens-node"; -import { NextResponse, NextRequest } from "next/server"; -import { withSession } from "supertokens-node/nextjs"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; -// @ts-ignore -import { backendConfig } from "@/app/config/backend"; - -SuperTokens.init(backendConfig()); - -export async function POST(request: NextRequest) { - return withSession(request, async (err, session) => { - if (err) { - return NextResponse.json(err, { status: 500 }); - } - // All validator checks have passed and the user has a verified email address - return NextResponse.json({ message: "Your email is verified!" }); - }, - { - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - } - ); -} -``` - - - - -```tsx -import { Controller, Post, UseGuards, Request, Response, Session } from "@nestjs/common"; -import { SessionContainer, SessionClaimValidator } from "supertokens-node/recipe/session"; -// @ts-ignore -import { AuthGuard } from './auth/auth.guard'; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -@Controller() -export class ExampleController { - @Post('example') - @UseGuards(new AuthGuard({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators: SessionClaimValidator[]) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - })) - async postExample(@Session() session: SessionContainer): Promise { - // All validator checks have passed and the user has a verified email address - return true; - } -} -``` - - - - - - - - -```go -import ( - "net/http" - - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - _ = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - session.VerifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }, exampleAPI).ServeHTTP(rw, r) - }) -} - -func exampleAPI(w http.ResponseWriter, r *http.Request) { - // TODO: session is verified and all validators have passed.. -} -``` - - - - -```go -import ( - "net/http" - - "github.com/gin-gonic/gin" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - router := gin.New() - - // Wrap the API handler in session.VerifySession - router.POST("/likecomment", verifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }), exampleAPI) -} - -// This is a function that wraps the supertokens verification function -// to work the gin -func verifySession(options *sessmodels.VerifySessionOptions) gin.HandlerFunc { - return func(c *gin.Context) { - session.VerifySession(options, func(rw http.ResponseWriter, r *http.Request) { - c.Request = c.Request.WithContext(r.Context()) - c.Next() - })(c.Writer, c.Request) - // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly - c.Abort() - } -} - -func exampleAPI(c *gin.Context) { - // TODO: session is verified and all claim validators pass. -} -``` - - - - -```go -import ( - "net/http" - - "github.com/go-chi/chi" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - r := chi.NewRouter() - - // Wrap the API handler in session.VerifySession - r.Post("/likecomment", session.VerifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }, exampleAPI)) -} - -func exampleAPI(w http.ResponseWriter, r *http.Request) { - // TODO: session is verified and all claim validators pass. -} -``` - - - - -```go -import ( - "net/http" - - "github.com/gorilla/mux" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - router := mux.NewRouter() - - // Wrap the API handler in session.VerifySession - router.HandleFunc("/likecomment", session.VerifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }, exampleAPI)).Methods(http.MethodPost) -} - -func exampleAPI(w http.ResponseWriter, r *http.Request) { - // TODO: session is verified and all claim validators pass. -} -``` - - - - - - - - - -```python -from supertokens_python.recipe.session.framework.fastapi import verify_session -from supertokens_python.recipe.emailverification import EmailVerificationClaim -from supertokens_python.recipe.session import SessionContainer -from fastapi import Depends - -@app.post('/like_comment') # type: ignore -async def like_comment(session: SessionContainer = Depends( - verify_session( - # highlight-start - # We add the EmailVerificationClaim's is_verified validator - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \ - [EmailVerificationClaim.validators.is_verified()] - # highlight-end - ) -)): - # All validator checks have passed and the user has a verified email address - pass -``` - - - - -```python -from supertokens_python.recipe.session.framework.flask import verify_session -from supertokens_python.recipe.emailverification import EmailVerificationClaim - -@app.route('/update-jwt', methods=['POST']) # type: ignore -@verify_session( - # highlight-start - # We add the EmailVerificationClaim's is_verified validator - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \ - [EmailVerificationClaim.validators.is_verified()] - # highlight-end -) -def like_comment(): - # All validator checks have passed and the user has a verified email address - pass -``` - - - - -```python -from supertokens_python.recipe.session.framework.django.asyncio import verify_session -from django.http import HttpRequest -from supertokens_python.recipe.emailverification import EmailVerificationClaim - -@verify_session( - # highlight-start - # We add the EmailVerificationClaim's is_verified validator - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \ - [EmailVerificationClaim.validators.is_verified()] - # highlight-end -) -async def like_comment(request: HttpRequest): - # All validator checks have passed and the user has a verified email address - pass -``` - - - - - - - -We add the SDK's `EmailVerificationClaim` validator to the `verifySession` middleware call as shown above, and that will only allow access if the email is verified, else it will return `403` to the frontend. - - - - - - - -import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" - -## Step 4: Protecting frontend routes - - - - - - - -```tsx -import Session from "supertokens-web-js/recipe/session"; -import { EmailVerificationClaim } from "supertokens-web-js/recipe/emailverification"; - -async function shouldLoadRoute(): Promise { - if (await Session.doesSessionExist()) { - // highlight-start - let validationErrors = await Session.validateClaims(); - - if (validationErrors.length === 0) { - // user has verified their email address - return true; - } else { - for (const err of validationErrors) { - if (err.id === EmailVerificationClaim.id) { - // email is not verified - } - } - } - // highlight-end - } - // a session does not exist, or email is not verified - return false -} -``` - - - - -```tsx -import supertokensSession from "supertokens-web-js-script/recipe/session"; -import supertokensEmailVerification from "supertokens-web-js-script/recipe/emailverification"; -async function shouldLoadRoute(): Promise { - if (await supertokensSession.doesSessionExist()) { - // highlight-start - let validationErrors = await supertokensSession.validateClaims(); - - if (validationErrors.length === 0) { - // user has verified their email address - return true; - } else { - for (const err of validationErrors) { - if (err.id === supertokensEmailVerification.EmailVerificationClaim.id) { - // email is not verified - } - } - } - // highlight-end - } - // a session does not exist, or email is not verified - return false -} -``` - - - - -In your protected routes, you need to first check if a session exists, and then call the `Session.validateClaims` function as shown above. This function inspects the session's contents and runs claim validators on them. If a claim validator fails, it will be reflected in the `validationErrors` variable. The `EmailVerificationClaim` validator will be automatically checked by this function since you have initialized the email verification recipe. - -
Handling 403 responses on the frontend - - - -If your frontend queries a protected API on your backend and it fails with a 403, you can call the `validateClaims` function and loop through the errors to know which claim has failed: - -```tsx -import axios from "axios"; -import Session from "supertokens-web-js/recipe/session"; -import { EmailVerificationClaim, sendVerificationEmail } from "supertokens-web-js/recipe/emailverification"; - -async function callProtectedRoute() { - try { - let response = await axios.get("^{form_apiDomain}/protectedroute"); - } catch (error) { - // highlight-start - if (axios.isAxiosError(error) && error.response?.status === 403) { - let validationErrors = await Session.validateClaims(); - for (let err of validationErrors) { - if (err.id === EmailVerificationClaim.id) { - // email verification claim check failed - // We call the sendEmail function defined in the next section to send the verification email. - // await sendEmail(); - } else { - // some other claim check failed (from the global validators list) - } - } - // highlight-end - - } - } -} -``` - - - -
- -
- - - - - - - -```tsx -import SuperTokens from 'supertokens-react-native'; - -async function checkIfEmailIsVerified() { - if (await SuperTokens.doesSessionExist()) { - - // highlight-start - let isVerified: boolean = (await SuperTokens.getAccessTokenPayloadSecurely())["st-ev"].v; - - if (isVerified) { - // TODO.. - } else { - // TODO.. - } - // highlight-end - } -} -``` - - - - - -```kotlin -import android.app.Application -import com.supertokens.session.SuperTokens -import org.json.JSONObject - -class MainApplication: Application() { - fun checkIfEmailIsVerified() { - val accessTokenPayload: JSONObject = SuperTokens.getAccessTokenPayloadSecurely(this); - val isVerified: Boolean = (accessTokenPayload.get("st-ev") as JSONObject).get("v") as Boolean - if (isVerified) { - // TODO.. - } else { - // TODO.. - } - } -} -``` - - - - - -```swift -import UIKit -import SuperTokensIOS - -fileprivate class ViewController: UIViewController { - func checkIfEmailIsVerified() { - if let accessTokenPayload: [String: Any] = try? SuperTokens.getAccessTokenPayloadSecurely(), let emailVerificationObject: [String: Any] = accessTokenPayload["st-ev"] as? [String: Any], let isVerified: Bool = emailVerificationObject["v"] as? Bool { - if isVerified { - // Email is verified - } else { - // Email is not verified - } - } - } -} -``` - - - - - -```dart -import 'package:supertokens_flutter/supertokens.dart'; - -Future checkIfEmailIsVerified() async { - var accessTokenPayload = await SuperTokens.getAccessTokenPayloadSecurely(); - - if (accessTokenPayload.containsKey("st-ev")) { - Map emailVerificationObject = accessTokenPayload["st-ev"]; - - if (emailVerificationObject.containsKey("v")) { - bool isVerified = emailVerificationObject["v"]; - - if (isVerified) { - // Email is verified - } else { - // Email is not verified - } - } - } -} -``` - - - - - -
Handling 403 responses on the frontend - -If your frontend queries a protected API on your backend and it fails with a 403, you can check the value of the `st-ev` claim in the access token payload. If it is set to false you can send the verification email - -
- -
-
- -import AppInfoForm from "/src/components/appInfoForm" - -## Step 5: Sending the email verification email - -When the email verification validators fail, or post sign up, you want to redirect the user to a screen telling them that a verification email has been sent to them. On this screen, you should call the following API - - - - - - - -```tsx -import { sendVerificationEmail } from "supertokens-web-js/recipe/emailverification"; - -async function sendEmail() { - try { - let response = await sendVerificationEmail(); - if (response.status === "EMAIL_ALREADY_VERIFIED_ERROR") { - // This can happen if the info about email verification in the session was outdated. - // Redirect the user to the home page - window.location.assign("/home"); - } else { - // email was sent successfully. - window.alert("Please check your email and click the link in it") - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - -```tsx -import supertokensEmailVerification from "supertokens-web-js/recipe/emailverification"; - -async function sendEmail() { - try { - let response = await supertokensEmailVerification.sendVerificationEmail(); - if (response.status === "EMAIL_ALREADY_VERIFIED_ERROR") { - // This can happen if the info about email verification in the session was outdated. - // Redirect the user to the home page - window.location.assign("/home"); - } else { - // email was sent successfully. - window.alert("Please check your email and click the link in it") - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - - - - - - - -You should create a new screen on your app that asks the user to enter their email to which an email will be sent. This screen should ideally be linked to from the sign in form. - -Once the user has enters their email, you can call the following API to send an email verification email to that user: - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/user/email/verify/token' \ ---header 'Authorization: Bearer ...' -``` - - - -The response body from the API call has a `status` property in it: -- `status: "OK"`: An email was sent to the user successfully. -- `status: "EMAIL_ALREADY_VERIFIED_ERROR"`: This status can be returned if the info about email verification in the session was outdated. Redirect the user to the home page. -- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. - - - -You do not need to add the tenant ID to the path here because the backend fetches the tenantId of the user from the session token. - - - - - - -:::note -The API for sending an email verification email requires an active session. If you are using our frontend SDKs, then the session tokens should automatically get attached to the request. -::: - -### Changing the email verification link domain / path -By default, the email verification link will point to the `websiteDomain` that is configured on the backend, on the `/auth/verify-email` route (where `/auth` is the default value of `websiteBasePath`). - -If you want to change this to a different path, a different domain, or deep link it to your mobile / desktop app, then you can do so on the backend in the following way: - - - - -```tsx -import SuperTokens from "supertokens-node"; -import EmailVerification from "supertokens-node/recipe/emailverification"; - -SuperTokens.init({ - supertokens: { - connectionURI: "...", - }, - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "..." - }, - recipeList: [ - EmailVerification.init({ - mode: "OPTIONAL", - // highlight-start - emailDelivery: { - override: (originalImplementation) => { - return { - ...originalImplementation, - sendEmail(input) { - return originalImplementation.sendEmail({ - ...input, - emailVerifyLink: input.emailVerifyLink.replace( - // This is: `${websiteDomain}${websiteBasePath}/verify-email` - "http://localhost:3000/auth/verify-email", - "http://localhost:3000/your/path" - ) - } - ) - }, - } - } - } - // highlight-end - }) - ] -}); -``` - - - -```go -import ( - "strings" - - "github.com/supertokens/supertokens-golang/ingredients/emaildelivery" - "github.com/supertokens/supertokens-golang/recipe/emailverification" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - supertokens.Init(supertokens.TypeInput{ - RecipeList: []supertokens.Recipe{ - emailverification.Init(evmodels.TypeInput{ - Mode: evmodels.ModeOptional, - // highlight-start - EmailDelivery: &emaildelivery.TypeInput{ - Override: func(originalImplementation emaildelivery.EmailDeliveryInterface) emaildelivery.EmailDeliveryInterface { - ogSendEmail := *originalImplementation.SendEmail - - (*originalImplementation.SendEmail) = func(input emaildelivery.EmailType, userContext supertokens.UserContext) error { - // This is: `${websiteDomain}${websiteBasePath}/verify-email` - input.EmailVerification.EmailVerifyLink = strings.Replace( - input.EmailVerification.EmailVerifyLink, - "http://localhost:3000/auth/verify-email", - "http://localhost:3000/your/path", 1, - ) - return ogSendEmail(input, userContext) - } - return originalImplementation - }, - }, - // highlight-end - }), - }, - }) -} -``` - - - -```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import emailverification -from supertokens_python.ingredients.emaildelivery.types import EmailDeliveryConfig -from supertokens_python.recipe.emailverification.types import EmailDeliveryOverrideInput, EmailTemplateVars -from typing import Dict, Any - - -def custom_email_delivery(original_implementation: EmailDeliveryOverrideInput) -> EmailDeliveryOverrideInput: - original_send_email = original_implementation.send_email - - # highlight-start - async def send_email(template_vars: EmailTemplateVars, user_context: Dict[str, Any]) -> None: - - # This is: `${websiteDomain}${websiteBasePath}/verify-email` - template_vars.email_verify_link = template_vars.email_verify_link.replace( - "http://localhost:3000/auth/verify-email", "http://localhost:3000/your/path") - - return await original_send_email(template_vars, user_context) - # highlight-end - - original_implementation.send_email = send_email - return original_implementation - - -init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore - recipe_list=[ - emailverification.init( - mode="OPTIONAL", - # highlight-next-line - email_delivery=EmailDeliveryConfig(override=custom_email_delivery)) - ] -) -``` - - - - - - - -For a multi tenant setup, the input to the `sendEmail` function will also contain the `tenantId`. You can use this to determine the correct value to set for the websiteDomain in the generated link. - - - -## Step 6: Verifying the email post link clicked - -Once the user clicks the email verification link, and it opens your app, you can call the following function which will automatically extract the token and tenantId (if using a multi tenant setup) from the link and call the token verification API. - - - - - - - -```tsx -import { verifyEmail } from "supertokens-web-js/recipe/emailverification"; - -async function consumeVerificationCode() { - try { - let response = await verifyEmail(); - if (response.status === "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR") { - // This can happen if the verification code is expired or invalid. - // You should ask the user to retry - window.alert("Oops! Seems like the verification link expired. Please try again") - window.location.assign("/auth/verify-email") // back to the email sending screen. - } else { - // email was verified successfully. - window.location.assign("/home") - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - -```tsx -import supertokensEmailVerification from "supertokens-web-js/recipe/emailverification"; - -async function consumeVerificationCode() { - try { - let response = await supertokensEmailVerification.verifyEmail(); - if (response.status === "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR") { - // This can happen if the verification code is expired or invalid. - // You should ask the user to retry - window.alert("Oops! Seems like the verification link expired. Please try again") - window.location.assign("/auth/verify-email") // back to the email sending screen. - } else { - // email was verified successfully. - window.location.assign("/home") - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - - - - - - - -Once the user clicks the email verification link, and it opens as a deep link into your mobile app, you can extract the token from the link and call the verification API as shown below: - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/user/email/verify' \ ---header 'Content-Type: application/json; charset=utf-8' \ ---data-raw '{ - "method": "token", - "token": "ZTRiOTBjNz...jI5MTZlODkxw" -}' -``` - - - - -For a multi tenancy setup, the `` value can be fetched from `tenantId` query parameter from the email verification link. If it's not there in the link, you can use the value `"public"` (which is the default tenant). - - - - - - -The response body from the API call has a `status` property in it: -- `status: "OK"`: Email verification was successful. -- `status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR"`: This can happen if the verification code is expired or invalid. You should ask the user to retry. -- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. - - - - -:::caution -- This API doesn't require an active session to succeed. -- If you are calling the above API on page load, there is an edge case in which email clients might open the verification link in the email (for scanning purposes) and consume the token in the URL. This would lead to issues in which an attacker could sign up using someone else's email and end up with a veriifed status! - - To prevent this, on page load, you should check if a session exists, and if it does, only then call the above API. If a session does not exist, you should first show a button, which when clicked would call the above API (email clients won't automatically click on this button). The button text could be something like "Click here to verify your email". -::: - - - - - - - -## See also - -- [Post email verification action](../common-customizations/email-verification/handling-email-verification-success) -- [Change email verification link's lifetime](../common-customizations/email-verification/changing-token-lifetime) -- [Customise email template or email delivery method](../email-delivery/about) -- [Manually changing email verification status for a user](../common-customizations/email-verification/changing-email-verification-status) -- [Generating email verification links manually](../common-customizations/email-verification/generate-link-manually) -- 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) in `REQUIRED` mode. - - \ No newline at end of file diff --git a/v2/passwordless/custom-ui/handling-session-tokens.mdx b/v2/passwordless/custom-ui/handling-session-tokens.mdx index bdfa1a21f..6ff934e75 100644 --- a/v2/passwordless/custom-ui/handling-session-tokens.mdx +++ b/v2/passwordless/custom-ui/handling-session-tokens.mdx @@ -4,412 +4,7 @@ title: Handling session tokens hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -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"; -import AppInfoForm from "/src/components/appInfoForm" -import {Question, Answer}from "/src/components/question" + -# Handling session tokens - -There are two modes ways in which you can use sessions with SuperTokens: -- Using `httpOnly` cookies -- Authorization bearer token. - -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 - -### For Web - - - - - -:::success -No action required. -::: - -Our frontend SDK handles everything for you. You only need to make sure that you have called `supertokens.init` before making any network requests. - -Our SDK adds interceptors to `fetch` and `XHR` (used by `axios`) to save and add session tokens from and to the request. - -By default, our web SDKs use cookies to provide credentials. - - - - - -import NetworkInterceptors from "/session/reusableMD/networkInterceptors.mdx" - -### For React-Native -Our frontend SDK handles everything for you. You only need to make sure that you have added our network interceptors as shown below - - - -:::note -By default our mobile SDKs use a bearer token in the Authorization header to provide credentials. -::: - -### For Android - - - - -```kotlin -import android.app.Application -import com.supertokens.session.SuperTokens -import com.supertokens.session.SuperTokensHttpURLConnection -import com.supertokens.session.SuperTokensPersistentCookieStore -import java.net.URL -import java.net.HttpURLConnection - -class MainApplication: Application() { - override fun onCreate() { - super.onCreate() - // TODO: Make sure to call SuperTokens.init - } - - fun makeRequest() { - val url = URL("") - val connection = SuperTokensHttpURLConnection.newRequest(url, object: SuperTokensHttpURLConnection.PreConnectCallback { - override fun doAction(con: HttpURLConnection?) { - // TODO: Use `con` to set request method, headers etc - } - }) - - // Handle response using connection object, for example: - if (connection.responseCode == 200) { - // TODO: implement - } - } -} -``` - -:::note -When making network requests you do not need to call `HttpURLConnection.connect` because SuperTokens does this for you. -::: - - - - -```kotlin -import android.content.Context -import com.supertokens.session.SuperTokens -import com.supertokens.session.SuperTokensInterceptor -import okhttp3.OkHttpClient -import retrofit2.Retrofit - -class NetworkManager { - fun getClient(context: Context): OkHttpClient { - val clientBuilder = OkHttpClient.Builder() - clientBuilder.addInterceptor(SuperTokensInterceptor()) - // TODO: Make sure to call SuperTokens.init - - val client = clientBuilder.build() - - // REQUIRED FOR RETROFIT ONLY - val instance = Retrofit.Builder() - .baseUrl("") - .client(client) - .build() - - return client - } - - fun makeRequest(context: Context) { - val client = getClient(context) - // Use client to make requests normally - } -} -``` - - - - -:::note -By default our mobile SDKs use a bearer token in the Authorization header to provide credentials. -::: - -### For iOS - - - - - -

Using URLSession.shared

- -```swift -import Foundation -import SuperTokensIOS - -fileprivate class NetworkManager { - func setupSuperTokensInterceptor() { - URLProtocol.registerClass(SuperTokensURLProtocol.self) - } -} -``` - -

Using a custom URLSession instance

- -```swift -import Foundation -import SuperTokensIOS - -fileprivate class NetworkManager { - func setupSuperTokensInterceptor() { - let configuration = URLSessionConfiguration.default - configuration.protocolClasses = [SuperTokensURLProtocol.self] - let session = URLSession(configuration: configuration) - - // Use session when making network requests - } -} -``` - -
- - - -```swift -import Foundation -import SuperTokensIOS -import Alamofire - -fileprivate class NetworkManager { - func setupSuperTokensInterceptor() { - let configuration = URLSessionConfiguration.af.default - configuration.protocolClasses = [SuperTokensURLProtocol.self] + (configuration.protocolClasses ?? []) - let session = Session(configuration: configuration) - - // Use session when making network requests - } -} -``` - - - -
- -:::note -By default our mobile SDKs use a bearer token in the Authorization header to provide credentials. -::: - -### For Flutter - - - - -You can make requests as you normally would with `http`, the only difference is that you import the client from the supertokens package instead. - -```dart -// Import http from the SuperTokens package -import 'package:supertokens_flutter/http.dart' as http; - -Future makeRequest() async { - Uri uri = Uri.parse("http://localhost:3001/api"); - var response = await http.get(uri); - // handle response -} -``` - -

Using a custom http client

- -If you use a custom http client and want to use SuperTokens, you can simply provide the SDK with your client. All requests will continue to use your client along with the session logic that SuperTokens provides. - -```dart -// Import http from the SuperTokens package -import 'package:supertokens_flutter/http.dart' as http; - -Future makeRequest() async { - Uri uri = Uri.parse("http://localhost:3001/api"); - - // Initialise your custom client - var customClient = http.Client(); - // provide your custom client to SuperTokens - var httpClient = http.Client(client: customClient); - - var response = await httpClient.get(uri); - // handle response -} -``` - -
- - -

Add the SuperTokens interceptor

- -Use the extension method provided by the SuperTokens SDK to enable interception on your Dio client. This allows the SuperTokens SDK to handle session tokens for you. - -```dart -import 'package:supertokens_flutter/dio.dart'; -import 'package:dio/dio.dart'; - -void setup() { - Dio dio = Dio(); // Create a Dio instance. - dio.addSupertokensInterceptor(); -} -``` - -

Making network requests

- -You can make requests as you normally would with `dio`. - -```dart -import 'package:supertokens_flutter/dio.dart'; -import 'package:dio/dio.dart'; - -void setup() { - Dio dio = Dio( - // Provide your config here - ); - dio.addSupertokensInterceptor(); - - var response = dio.get("http://localhost:3001/api"); - // handle response -} -``` - -
-
- -:::note -By default our mobile SDKs use a bearer token in the Authorization header to provide credentials. -::: - -## If not using our frontend SDK - -:::caution -We highly recommend using our frontend SDK to handle session token management. It will save you a lot of time. -::: - -In this case, you will need to manually handle the tokens and session refreshing, and decide if you are going to use header or cookie-based sessions. - -For browsers, we recommend cookies, while for mobile apps (or if you don't want to use the built-in cookie manager) you should use header-based sessions. - - - - - -### On login - -You should attach the `st-auth-mode` header to calls to the login API, but this header is safe to attach to all requests. In this case it should be set to "cookie". - -The login API will return the following headers: -- `Set-Cookie`: This will contain the `sAccessToken`, `sRefreshToken` cookies which will be `httpOnly` and will be automatically mananaged by the browser. For mobile apps, you will need to setup cookie handling yourself, use our SDK or use a header based authentication mode. -- `front-token` header: This contains information about the access token: - - The userID - - The expiry time of the access token - - The payload added by you in the access token. - - Here is the structure of the token: - ```tsx - let frontTokenFromRequestHeader = "..."; - let frontTokenDecoded = JSON.parse(decodeURIComponent(escape(atob(frontTokenFromRequestHeader)))); - console.log(frontTokenDecoded); - /* - { - ate: 1665226412455, // time in milliseconds for when the access token will expire, and then a refresh is required - uid: "....", // user ID - up: { - sub: "..", - iat: .., - ... // other access token payload - } - } - - */ - ``` - - This token is mainly used for cookie based auth because you don't have access to the actual access token on the frontend (for security reasons), but may want to read its payload (for example to know the user's role). This token itself is not signed and hence can't be used in place of the access token itself. You may want to save this token in localstorage or in frontend cookies (using `document.cookies`). - -- `anti-csrf` header (optional): By default it's not required, so it's not sent. But if this is sent, you should save this token as well for use when making requests. - -### Making network requests to protected APIs - -The `sAccessToken` will get attached to the request automatically by the browser. Other than that, you need to add the following headers to the request: -- `rid: "anti-csrf"` - this prevents against anti-CSRF requests. If your `apiDomain` and `websiteDomain` values are exactly the same, then this is not necessary. -- `anti-csrf` header (optional): If this was provided to you during login, then you need to add that token as the value of this header. -- You need to set the `credentials` header to `true` or `include`. This is achieved different based on the library you are using to make requests. - -An API call can potentially update the `sAccessToken` and `front-token` tokens, for example if you call the `mergeIntoAccessTokenPayload` function on the `session` object on the backend. This kind of update is reflected in the response headers for your API calls. The headers will contain new values for: -- `sAccessToken`: This will be as a new `Set-Cookie` header and will be managed by the browser automatically. -- `front-token`: This should be read and saved by you in the same way as it's being done during login. - -### Handling session refreshing -If any of your API calls return with a status code of `401`, it means that the access token has expired. This will require you to refresh the session before retrying the same API call. - -You can call the refresh API as follows: - - - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/session/refresh' \ ---header 'Cookie: sRefreshToken=...' -``` - -:::note -- You may also need to add the `anti-csrf` header to the request if that was provided to you during sign in. -- The cURL command above shows the `sRefreshToken` cookie as well, but this is added by the web browser automatically, so you don't need to add it explicitly. -::: - - - -The result of a session refresh will be either: -- Status code `200`: This implies a succesful refresh. The set of tokens returned here will be the same as when the user logs in, so you can handle them in the same way. -- Status code `401`: This means that the refresh token is invalid, or has been revoked. You must ask the user to relogin. Remember to clear the `front-token` that you saved on the frontend earlier. - - - - - -### On login - -You should attach the `st-auth-mode` header to calls to the login API, but this header is safe to attach to all requests. In this case it should be set to "header". - -The login API will return the following headers: -- `st-access-token`: This contains the current access token associated with the session. You should save this in your application (e.g., in frontend localstorage). -- `st-refresh-token`: This contains the current refresh token associated with the session. You should save this in your application (e.g., in frontend localstorage). - -### Making network requests to protected APIs - -You need to add the following headers to request: -- `authorization: Bearer {access-token}` -- You need to set the `credentials` to `true` or `include`. This is achieved different based on the library you are using to make requests. - -An API call can potentially update the `access-token`, for example if you call the `mergeIntoAccessTokenPayload` function on the `session` object on the backend. This kind of update is reflected in the response headers for your API calls. The headers will contain new values for `st-access-token` - -These should be read and saved by you in the same way as it's being done during login. - -### Handling session refreshing -If any of your API calls return with a status code of `401`, it means that the access token has expired. This will require you to refresh the session before retrying the same API call. - -You can call the refresh API as follows: - - - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/session/refresh' \ ---header 'authorization: Bearer {refresh-token}' -``` - - - -The result of a session refresh will be either: -- Status code `200`: This implies a succesful refresh. The set of tokens returned here will be the same as when the user logs in, so you can handle them in the same way. -- Status code `401`: This means that the refresh token is invalid, or has been revoked. You must ask the user to relogin. Remember to clear the `st-refresh-token` and `st-access-token` that you saved on the frontend earlier. - - - - diff --git a/v2/passwordless/custom-ui/init/backend.mdx b/v2/passwordless/custom-ui/init/backend.mdx index 67db5fb3c..577fc5a32 100644 --- a/v2/passwordless/custom-ui/init/backend.mdx +++ b/v2/passwordless/custom-ui/init/backend.mdx @@ -4,973 +4,8 @@ title: "Step 2: Backend" hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import TabItem from '@theme/TabItem'; -import NodeJSFrameworkSubTabsServerless from "/src/components/tabs/NodeJSFrameworkSubTabsServerless" -import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs" -import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" -import AppInfoForm from "/src/components/appInfoForm" -import { PasswordlessBackendForm } from "/src/components/snippetConfigForm/passwordlessBackendForm"; -import CoreInjector from "/src/components/coreInjector" -import { Question, Answer }from "/src/components/question" -import BackendSDKCasing from "/src/components/BackendSDKCasing" -import BackendDeliveryMethod from "../../reusableMD/backendDeliveryMethod.mdx" -# Backend Integration + -## Supported frameworks - -import TechStackSupport from "../../../community/reusableMD/supported-tech-stacks-backend.mdx" - - - -## 1) Install - - - - -```bash -npm i -s supertokens-node -``` - - - - -```bash -go get github.com/supertokens/supertokens-golang -``` - - - - -```bash -pip install supertokens-python -``` - - - - -## 2) Initialise SuperTokens - - - - - - -Add the code below to your server's init file. - - - - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import Passwordless from "supertokens-node/recipe/passwordless"; - -supertokens.init({ - framework: "express", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - Passwordless.init({ - flowType: "^{form_flowType}", - contactMethod: "^{form_contactMethod}" - }), - Session.init() // initializes session features - ] -}); -``` - - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import Passwordless from "supertokens-node/recipe/passwordless"; - -supertokens.init({ - framework: "hapi", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - Passwordless.init({ - flowType: "^{form_flowType}", - contactMethod: "^{form_contactMethod}" - }), - Session.init() // initializes session features - ] -}); -``` - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import Passwordless from "supertokens-node/recipe/passwordless"; - -supertokens.init({ - framework: "fastify", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - Passwordless.init({ - flowType: "^{form_flowType}", - contactMethod: "^{form_contactMethod}" - }), - Session.init() // initializes session features - ] -}); -``` - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import Passwordless from "supertokens-node/recipe/passwordless"; - -supertokens.init({ - framework: "koa", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - Passwordless.init({ - flowType: "^{form_flowType}", - contactMethod: "^{form_contactMethod}" - }), - Session.init() // initializes session features - ] -}); -``` - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import Passwordless from "supertokens-node/recipe/passwordless"; - -supertokens.init({ - framework: "loopback", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - Passwordless.init({ - flowType: "^{form_flowType}", - contactMethod: "^{form_contactMethod}" - }), - Session.init() // initializes session features - ] -}); -``` - - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/passwordless" - "github.com/supertokens/supertokens-golang/recipe/passwordless/plessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - apiBasePath := "^{form_apiBasePath}" - websiteBasePath := "^{form_websiteBasePath}" - err := supertokens.Init(supertokens.TypeInput{ - Supertokens: &supertokens.ConnectionInfo{ - ^{coreInjector_connection_uri_comment} - ConnectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}APIKey: ^{coreInjector_api_key}, - }, - AppInfo: supertokens.AppInfo{ - AppName: "^{form_appName}", - APIDomain: "^{form_apiDomain}", - WebsiteDomain: "^{form_websiteDomain}", - APIBasePath: &apiBasePath, - WebsiteBasePath: &websiteBasePath, - }, - RecipeList: []supertokens.Recipe{ - passwordless.Init(plessmodels.TypeInput{ - FlowType: "^{form_flowType}", - ^{form_contactMethod_sendCB_Go} - }), - session.Init(nil), // initializes session features - }, - }) - - if err != nil { - panic(err.Error()) - } -} -``` - - - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig -from supertokens_python.recipe import passwordless, session - -^{form_contactMethod_import_Python} -init( - app_info=InputAppInfo( - app_name="^{form_appName}", - api_domain="^{form_apiDomain}", - website_domain="^{form_websiteDomain}", - api_base_path="^{form_apiBasePath}", - website_base_path="^{form_websiteBasePath}" - ), - supertokens_config=SupertokensConfig( - ^{coreInjector_connection_uri_comment_with_hash} - connection_uri=^{coreInjector_uri} - ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} - ), - framework='fastapi', - recipe_list=[ - session.init(), # initializes session features - passwordless.init( - flow_type="^{form_flowType}", - contact_config=^{form_contactMethod_initialize_Python}() - ) - ], - mode='asgi' # use wsgi if you are running using gunicorn -) -``` - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig -from supertokens_python.recipe import passwordless, session - -^{form_contactMethod_import_Python} -init( - app_info=InputAppInfo( - app_name="^{form_appName}", - api_domain="^{form_apiDomain}", - website_domain="^{form_websiteDomain}", - api_base_path="^{form_apiBasePath}", - website_base_path="^{form_websiteBasePath}" - ), - supertokens_config=SupertokensConfig( - ^{coreInjector_connection_uri_comment_with_hash} - connection_uri=^{coreInjector_uri} - ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} - ), - framework='flask', - recipe_list=[ - session.init(), # initializes session features - passwordless.init( - flow_type="^{form_flowType}", - contact_config=^{form_contactMethod_initialize_Python}() - ) - ] -) -``` - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig -from supertokens_python.recipe import passwordless, session - -^{form_contactMethod_import_Python} -init( - app_info=InputAppInfo( - app_name="^{form_appName}", - api_domain="^{form_apiDomain}", - website_domain="^{form_websiteDomain}", - api_base_path="^{form_apiBasePath}", - website_base_path="^{form_websiteBasePath}" - ), - supertokens_config=SupertokensConfig( - ^{coreInjector_connection_uri_comment_with_hash} - connection_uri=^{coreInjector_uri} - ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} - ), - framework='django', - recipe_list=[ - session.init(), # initializes session features - passwordless.init( - flow_type="^{form_flowType}", - contact_config=^{form_contactMethod_initialize_Python}() - ) - ], - mode='asgi' # use wsgi if you are running django server in sync mode -) -``` - - - - - - - - - - - - -## 3) Add the SuperTokens APIs & CORS setup - - - - - - - - -:::important -- Add the `middleware` BEFORE all your routes. -- Add the `cors` middleware BEFORE the SuperTokens middleware as shown below. -::: - -```tsx -import express from "express"; -import cors from "cors"; -import supertokens from "supertokens-node"; -import {middleware} from "supertokens-node/framework/express"; - -let app = express(); - -app.use(cors({ - // highlight-start - origin: "^{form_websiteDomain}", - allowedHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], - credentials: true, - // highlight-end -})); - -// IMPORTANT: CORS should be before the below line. -// highlight-next-line -app.use(middleware()); - -// ...your API routes -``` -This `middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): -- `POST ^{form_apiBasePath}/signinup/code`: For starting the passwordless login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/resend`: To generate and resend a code during an already started login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/consume`: For finishing the passwordless login/sign up process -- `GET ^{form_apiBasePath}/passwordless/email/exists`: To check if an email is already signed up -- `GET ^{form_apiBasePath}/passwordless/phonenumber/exists`: To check if a phonenumber is already signed up - - - - -Register the `plugin`. - -```tsx -import Hapi from "@hapi/hapi"; -import supertokens from "supertokens-node"; -import { plugin } from "supertokens-node/framework/hapi"; - -const server = Hapi.server({ - port: 8000, - routes: { - // highlight-start - cors: { - origin: ["^{form_websiteDomain}"], - additionalHeaders: [...supertokens.getAllCORSHeaders()], - credentials: true, - } - // highlight-end - } -}); - -(async() => { - // highlight-next-line - await server.register(plugin); - - await server.start(); -})(); - -// ...your API routes -``` -This `plugin` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)) as well take care of all the errors thrown by the Supertokens library: -- `POST ^{form_apiBasePath}/signinup/code`: For starting the passwordless login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/resend`: To generate and resend a code during an already started login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/consume`: For finishing the passwordless login/sign up process -- `GET ^{form_apiBasePath}/passwordless/email/exists`: To check if an email is already signed up -- `GET ^{form_apiBasePath}/passwordless/phonenumber/exists`: To check if a phonenumber is already signed up - - - - - -Register the `plugin`. Also register [`@fastify/formbody`](https://github.com/fastify/fastify-formbody) plugin. - -```tsx -import cors from "@fastify/cors"; -import supertokens from "supertokens-node"; -import { plugin } from "supertokens-node/framework/fastify"; -import formDataPlugin from "@fastify/formbody"; - -import fastifyImport from "fastify"; -let fastify = fastifyImport(); - -// ...other middlewares -(async () => { - // highlight-start - await fastify.register(cors, { - origin: "^{form_websiteDomain}", - allowedHeaders: ['Content-Type', ...supertokens.getAllCORSHeaders()], - credentials: true, - }) - // highlight-end - // highlight-next-line - await fastify.register(formDataPlugin); - // highlight-next-line - await fastify.register(plugin); - - await fastify.listen(3000); -})(); - -// ...your API routes -``` -This `plugin` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): -- `POST ^{form_apiBasePath}/signinup/code`: For starting the passwordless login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/resend`: To generate and resend a code during an already started login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/consume`: For finishing the passwordless login/sign up process -- `GET ^{form_apiBasePath}/passwordless/email/exists`: To check if an email is already signed up -- `GET ^{form_apiBasePath}/passwordless/phonenumber/exists`: To check if a phonenumber is already signed up - - - - - -:::important -Add the `middleware` BEFORE all your routes. -::: - -```tsx -import Koa from "koa"; -import cors from '@koa/cors'; -import supertokens from "supertokens-node"; -import { middleware } from "supertokens-node/framework/koa"; - -let app = new Koa(); - -// ...other middlewares -app.use(cors({ - // highlight-start - origin: "^{form_websiteDomain}", - allowHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], - credentials: true, - // highlight-end -})); - -// highlight-next-line -app.use(middleware()); - -// ...your API routes -``` -This `middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): -- `POST ^{form_apiBasePath}/signinup/code`: For starting the passwordless login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/resend`: To generate and resend a code during an already started login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/consume`: For finishing the passwordless login/sign up process -- `GET ^{form_apiBasePath}/passwordless/email/exists`: To check if an email is already signed up -- `GET ^{form_apiBasePath}/passwordless/phonenumber/exists`: To check if a phonenumber is already signed up - - - - - -:::important -Add the `middleware` BEFORE all your routes. -::: - -```tsx -import {RestApplication} from '@loopback/rest'; -import supertokens from "supertokens-node"; -import {middleware} from "supertokens-node/framework/loopback"; - -let app = new RestApplication({ - rest: { - cors: { - // highlight-start - origin: "^{form_websiteDomain}", - allowedHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], - credentials: true - // highlight-end - } - } -}); - -// highlight-next-line -app.middleware(middleware); - -// ...your API routes -``` -This `middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): -- `POST ^{form_apiBasePath}/signinup/code`: For starting the passwordless login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/resend`: To generate and resend a code during an already started login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/consume`: For finishing the passwordless login/sign up process -- `GET ^{form_apiBasePath}/passwordless/email/exists`: To check if an email is already signed up -- `GET ^{form_apiBasePath}/passwordless/phonenumber/exists`: To check if a phonenumber is already signed up - - - - - - - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "net/http" - "strings" - - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - //highlight-start - http.ListenAndServe("SERVER ADDRESS", corsMiddleware( - supertokens.Middleware(http.HandlerFunc(func(rw http.ResponseWriter, - //highlight-end - r *http.Request) { - // TODO: Handle your APIs.. - - })))) -} - -func corsMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(response http.ResponseWriter, r *http.Request) { - response.Header().Set("Access-Control-Allow-Origin", "^{form_websiteDomain}") - response.Header().Set("Access-Control-Allow-Credentials", "true") - if r.Method == "OPTIONS" { - // we add content-type + other headers used by SuperTokens - response.Header().Set("Access-Control-Allow-Headers", - strings.Join(append([]string{"Content-Type"}, - //highlight-start - supertokens.GetAllCORSHeaders()...), ",")) - //highlight-end - response.Header().Set("Access-Control-Allow-Methods", "*") - response.Write([]byte("")) - } else { - next.ServeHTTP(response, r) - } - }) -} -``` - - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "net/http" - - "github.com/gin-contrib/cors" - "github.com/gin-gonic/gin" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - router := gin.New() - - // CORS - router.Use(cors.New(cors.Config{ - AllowOrigins: []string{"^{form_websiteDomain}"}, - AllowMethods: []string{"GET", "POST", "DELETE", "PUT", "OPTIONS"}, - AllowHeaders: append([]string{"content-type"}, - // highlight-next-line - supertokens.GetAllCORSHeaders()...), - AllowCredentials: true, - })) - - // Adding the SuperTokens middleware - // highlight-start - router.Use(func(c *gin.Context) { - supertokens.Middleware(http.HandlerFunc( - func(rw http.ResponseWriter, r *http.Request) { - c.Next() - })).ServeHTTP(c.Writer, c.Request) - // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly - c.Abort() - }) - // highlight-end - - // Add APIs and start server -} -``` - - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "github.com/go-chi/chi" - "github.com/go-chi/cors" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - r := chi.NewRouter() - - // CORS - r.Use(cors.Handler(cors.Options{ - AllowedOrigins: []string{"^{form_websiteDomain}"}, - AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, - AllowedHeaders: append([]string{"Content-Type"}, - //highlight-next-line - supertokens.GetAllCORSHeaders()...), - AllowCredentials: true, - })) - - // SuperTokens Middleware - //highlight-next-line - r.Use(supertokens.Middleware) - - // Add APIs and start server -} -``` - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "net/http" - - "github.com/gorilla/handlers" - "github.com/gorilla/mux" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - // Add APIs - - router := mux.NewRouter() - - // Adding handlers.CORS(options)(supertokens.Middleware(router))) - //highlight-start - http.ListenAndServe("SERVER ADDRESS", handlers.CORS( - handlers.AllowedHeaders(append([]string{"Content-Type"}, - supertokens.GetAllCORSHeaders()...)), - handlers.AllowedMethods([]string{"GET", "POST", "PUT", "HEAD", "OPTIONS"}), - handlers.AllowedOrigins([]string{"^{form_websiteDomain}"}), - handlers.AllowCredentials(), - )(supertokens.Middleware(router))) - //highlight-end -} -``` - - - -This `Middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): -- `POST ^{form_apiBasePath}/signinup/code`: For starting the passwordless login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/resend`: To generate and resend a code during an already started login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/consume`: For finishing the passwordless login/sign up process -- `GET ^{form_apiBasePath}/passwordless/email/exists`: To check if an email is already signed up -- `GET ^{form_apiBasePath}/passwordless/phonenumber/exists`: To check if a phonenumber is already signed up - - - - - - - - -Use the `Middleware` (**BEFORE all your routes**) and the `get_all_cors_headers()` functions as shown below. - -```python -from supertokens_python import get_all_cors_headers -from fastapi import FastAPI -from starlette.middleware.cors import CORSMiddleware -from supertokens_python.framework.fastapi import get_middleware - -app = FastAPI() -# highlight-next-line -app.add_middleware(get_middleware()) - -# TODO: Add APIs - -app.add_middleware( - CORSMiddleware, - allow_origins=[ - "^{form_websiteDomain}" - ], - allow_credentials=True, - allow_methods=["GET", "PUT", "POST", "DELETE", "OPTIONS", "PATCH"], - # highlight-next-line - allow_headers=["Content-Type"] + get_all_cors_headers(), -) - -# TODO: start server -``` - - - - -- Use the `Middleware` (**BEFORE all your routes and after calling init function**) and the `get_all_cors_headers()` functions as shown below. -- Add a route to catch all paths and return a 404. This is needed because if we don't add this, then OPTIONS request for the APIs exposed by the `Middleware` will return a `404`. - -```python -from supertokens_python import get_all_cors_headers -from flask import Flask, abort -from flask_cors import CORS # type: ignore -from supertokens_python.framework.flask import Middleware - -app = Flask(__name__) -# highlight-next-line -Middleware(app) - -# TODO: Add APIs - -CORS( - app=app, - origins=[ - "^{form_websiteDomain}" - ], - supports_credentials=True, - # highlight-next-line - allow_headers=["Content-Type"] + get_all_cors_headers(), -) - -# This is required since if this is not there, then OPTIONS requests for -# the APIs exposed by the supertokens' Middleware will return a 404 -# highlight-start -@app.route('/', defaults={'u_path': ''}) # type: ignore -@app.route('/') # type: ignore -def catch_all(u_path: str): - abort(404) -# highlight-end - -# TODO: start server -``` - - - - -Use the `Middleware` and the `get_all_cors_headers()` functions as shown below in your `settings.py`. - -```python -from supertokens_python import get_all_cors_headers -from typing import List -from corsheaders.defaults import default_headers - -CORS_ORIGIN_WHITELIST = [ - "^{form_websiteDomain}" -] - -CORS_ALLOW_CREDENTIALS = True - -CORS_ALLOWED_ORIGINS = [ - "^{form_websiteDomain}" -] - -CORS_ALLOW_HEADERS: List[str] = list(default_headers) + [ - "Content-Type" - # highlight-next-line -] + get_all_cors_headers() - -INSTALLED_APPS = [ - 'corsheaders', - 'supertokens_python' -] - -MIDDLEWARE = [ - 'corsheaders.middleware.CorsMiddleware', - ..., - # highlight-next-line - 'supertokens_python.framework.django.django_middleware.middleware', -] -``` - - - - -This `Middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): -- `POST ^{form_apiBasePath}/signinup/code`: For starting the passwordless login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/resend`: To generate and resend a code during an already started login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/consume`: For finishing the passwordless login/sign up process -- `GET ^{form_apiBasePath}/passwordless/email/exists`: To check if an email is already signed up -- `GET ^{form_apiBasePath}/passwordless/phonenumber/exists`: To check if a phonenumber is already signed up - - - - - - - -## 4) Add the SuperTokens error handler -Add the `errorHandler` **AFTER all your routes**, but **BEFORE your error handler** - - - - - - -```tsx -import express from "express"; -import {errorHandler} from "supertokens-node/framework/express"; - -const app = express(); -// ...your API routes - -// highlight-start -// Add this AFTER all your routes -app.use(errorHandler()) -// highlight-end - -// your own error handler -app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => { - // TODO -}); - -``` - - - -No additional `errorHandler` is required. - - - - -Add the `errorHandler` **Before all your routes and plugin registration** - -```tsx -import Fastify from "fastify"; -import {errorHandler} from "supertokens-node/framework/fastify"; - -const fastify = Fastify(); - -// highlight-next-line -fastify.setErrorHandler(errorHandler()); - -// ...your API routes - -``` - - - -No additional `errorHandler` is required. - - - - -No additional `errorHandler` is required. - - - - - - -:::info -You can skip this step -::: - - - - -:::info -You can skip this step -::: - - - - -## 5) Setup the SuperTokens core - -You need to now setup an instance of the SuperTokens core for your app (that your backend should connect to). You have two options: -- [Managed service](./core/saas-setup) -- Self hosted with your own database ([With Docker](./core/with-docker) or [Without Docker](./core/without-docker)) diff --git a/v2/passwordless/custom-ui/init/core/managed-service.mdx b/v2/passwordless/custom-ui/init/core/managed-service.mdx index 5dade6aca..726e4dc4a 100644 --- a/v2/passwordless/custom-ui/init/core/managed-service.mdx +++ b/v2/passwordless/custom-ui/init/core/managed-service.mdx @@ -4,95 +4,9 @@ title: Managed Service hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import TabItem from '@theme/TabItem'; -# Managed Service + -## Creating a development environment ✨ -- First, please [sign up](https://supertokens.com/auth) -- Select the auth method you want to use and follow the guided steps to integrate with our frontend and backend SDK if you have not done this already -Integration with SuperTokens SDKs -- Select a region and click the deploy button: -:::tip -You should select a region that is closest to your backend. -::: - -Deploying SuperTokens Core - -- After the deployment is complete the dashboard will look similar to this: -Deployed SuperTokens Core - -## Connecting the backend SDK with SuperTokens 🔌 -- Add the `connectionURI` and the `apiKey` shown on the dashboard to your code on the backend. - -SuperTokens managed service dashboard connectionURI and API key - - - - -```tsx -import supertokens from "supertokens-node"; - -supertokens.init({ - // highlight-start - supertokens: { - connectionURI: "", - apiKey: "" - }, - // highlight-end - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "..." - }, - recipeList: [] -}); -``` - - - - -```go -import "github.com/supertokens/supertokens-golang/supertokens" - -func main() { - supertokens.Init(supertokens.TypeInput{ - // highlight-start - Supertokens: &supertokens.ConnectionInfo{ - ConnectionURI: "", - APIKey: "", - }, - // highlight-end - }) -} - -``` - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig - -init( - app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), - # highlight-start - supertokens_config=SupertokensConfig( - connection_uri='', - api_key='' - ), - # highlight-end - framework='...', # type: ignore - recipe_list=[ - #... - ] -) -``` - - - diff --git a/v2/passwordless/custom-ui/init/core/self-hosted-with-docker.mdx b/v2/passwordless/custom-ui/init/core/self-hosted-with-docker.mdx index 1df643ac0..e31d7f6e6 100644 --- a/v2/passwordless/custom-ui/init/core/self-hosted-with-docker.mdx +++ b/v2/passwordless/custom-ui/init/core/self-hosted-with-docker.mdx @@ -4,268 +4,8 @@ title: With Docker hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import DatabaseTabs from "/src/components/tabs/DatabaseTabs" -import TabItem from '@theme/TabItem'; -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import DockerVersionProvider from "/src/components/dockerVersionProvider"; -# With Docker + -## Running the docker image 🚀 - - - - - - -```bash -docker run -p 3567:3567 -d registry.supertokens.io/supertokens/supertokens-mysql^{docker_version_mysql} -``` - -- To see all the env variables available, please see [the README file](https://github.com/supertokens/supertokens-docker-mysql/blob/master/README.md). -- The above command will start the container with an in-memory database. This means you **do not need to connect it to MySQL to test out SuperTokens**. -- When you are ready to connect it to your database, please visit the [Database setup section](../database-setup/mysql) - - - - - -```bash -docker run -p 3567:3567 -d registry.supertokens.io/supertokens/supertokens-postgresql^{docker_version_postgresql} -``` - -- To see all the env variables available, please see [the README file](https://github.com/supertokens/supertokens-docker-postgresql/blob/master/README.md). -- The above command will start the container with an in-memory database. This means you **do not need to connect it to PostgreSQL to test out SuperTokens**. -- When you are ready to connect it to your database, please visit the [Database setup section](../database-setup/postgresql) - - - - - -```bash -docker run -p 3567:3567 -d registry.supertokens.io/supertokens/supertokens-mongodb^{docker_version_mongodb} -``` - -- To see all the env variables available, please see [the README file](https://github.com/supertokens/supertokens-docker-mongodb/blob/master/README.md). -- The above command will start the container with an in-memory database. This means you **do not need to connect it to MongoDB to test out SuperTokens**. -- When you are ready to connect it to your database, please visit the [Database setup section](../database-setup/mongodb) - -:::caution -We do not offer login functionality with MongDB yet. We only offer session management. -::: - - - - - -## Testing that the service is running 🤞 -Open a browser and visit `http://localhost:3567/hello`. If you see a page that says `Hello` back, then the container was started successfully! - -If you are having issues with starting the docker image, please feel free to reach out to us [over email](mailto:team@supertokens.com) or [via Discord](https://supertokens.com/discord). - -:::tip -The `/hello` route checks whether the database connection is set up correctly and will only return a 200 status code if there is no issue. - -If you are using kubernetes or docker swarm, this endpoint is perfect for doing readiness and liveness probes. -::: - -## Connecting the backend SDK with SuperTokens 🔌 -- The default `port` for SuperTokens is `3567`. You can change this by binding a different port in the `docker run` command. For example, `docker run -p 8080:3567` will run SuperTokens on port `8080` on your machine. -- The connection info will go in the `supertokens` object in the `init` function on your backend: - - - - -```tsx -import supertokens from "supertokens-node"; - -supertokens.init({ - // highlight-start - supertokens: { - connectionURI: "http://localhost:3567", - apiKey: "someKey" // OR can be undefined - }, - // highlight-end - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "..." - }, - recipeList: [] -}); -``` - - - - -```go -import "github.com/supertokens/supertokens-golang/supertokens" - -func main() { - supertokens.Init(supertokens.TypeInput{ - // highlight-start - Supertokens: &supertokens.ConnectionInfo{ - ConnectionURI: "http://localhost:3567", - APIKey: "someKey", - }, - // highlight-end - }) -} - -``` - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig - -init( - app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), - # highlight-start - supertokens_config=SupertokensConfig( - connection_uri='http://localhost:3567', - api_key='someKey' - ), - # highlight-end - framework='...', # type: ignore - recipe_list=[ - #... - ] -) -``` - - - - -:::tip Security -There is no API key by default. Visit the "Auth flow customization" -> "SuperTokens core settings" -> "Adding API Keys" section to see how to add one. -::: - -## Docker compose file - - - - - - -```bash -version: '3' - -services: - db: - image: mysql:latest - environment: - MYSQL_ROOT_PASSWORD: root - MYSQL_USER: supertokens_user - MYSQL_PASSWORD: somePassword - MYSQL_DATABASE: supertokens - ports: - - 3306:3306 - networks: - - app_network - restart: unless-stopped - healthcheck: - test: [ "CMD", "mysqladmin", "ping", "-h", "localhost" ] - timeout: 20s - retries: 10 - - supertokens: - image: registry.supertokens.io/supertokens/supertokens-mysql^{docker_version_mysql} - depends_on: - db: - condition: service_healthy - ports: - - 3567:3567 - environment: - MYSQL_CONNECTION_URI: mysql://supertokens_user:somePassword@db:3306/supertokens - networks: - - app_network - restart: unless-stopped - healthcheck: - test: > - bash -c 'exec 3<>/dev/tcp/127.0.0.1/3567 && echo -e "GET /hello HTTP/1.1\r\nhost: 127.0.0.1:3567\r\nConnection: close\r\n\r\n" >&3 && cat <&3 | grep "Hello"' - interval: 10s - timeout: 5s - retries: 5 - -networks: - app_network: - driver: bridge -``` - - - - - -```bash -version: '3' - -services: - # Note: If you are assigning a custom name to your db service on the line below, make sure it does not contain underscores - db: - image: 'postgres:latest' - environment: - POSTGRES_USER: supertokens_user - POSTGRES_PASSWORD: somePassword - POSTGRES_DB: supertokens - ports: - - 5432:5432 - networks: - - app_network - restart: unless-stopped - healthcheck: - test: ['CMD', 'pg_isready', '-U', 'supertokens_user', '-d', 'supertokens'] - interval: 5s - timeout: 5s - retries: 5 - - supertokens: - image: registry.supertokens.io/supertokens/supertokens-postgresql^{docker_version_postgresql} - depends_on: - db: - condition: service_healthy - ports: - - 3567:3567 - environment: - POSTGRESQL_CONNECTION_URI: "postgresql://supertokens_user:somePassword@db:5432/supertokens" - networks: - - app_network - restart: unless-stopped - healthcheck: - test: > - bash -c 'exec 3<>/dev/tcp/127.0.0.1/3567 && echo -e "GET /hello HTTP/1.1\r\nhost: 127.0.0.1:3567\r\nConnection: close\r\n\r\n" >&3 && cat <&3 | grep "Hello"' - interval: 10s - timeout: 5s - retries: 5 - -networks: - app_network: - driver: bridge -``` - - - - - - -We are working on adding this section. - - - - - - -:::important -If you are running the backend process that integrates with our backend sdk as part of the docker compose file as well, make sure to use `http://supertokens:3567` as the connection uri instead of `http://localhost:3567`. -::: - - -## Helm charts for Kubernetes - -- For [MySQL image](https://github.com/supertokens/supertokens-docker-mysql/tree/master/helm-chart) - -- For [PostgreSQL image](https://github.com/supertokens/supertokens-docker-postgresql/tree/master/helm-chart) diff --git a/v2/passwordless/custom-ui/init/core/self-hosted-without-docker.mdx b/v2/passwordless/custom-ui/init/core/self-hosted-without-docker.mdx index 9a44a4e8c..7068a55cb 100644 --- a/v2/passwordless/custom-ui/init/core/self-hosted-without-docker.mdx +++ b/v2/passwordless/custom-ui/init/core/self-hosted-without-docker.mdx @@ -4,165 +4,9 @@ title: Without Docker hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import OSTabs from "/src/components/tabs/OSTabs"; -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -# Binary Installation + -## 1) Download SuperTokens -- Visit the [open source download page](https://supertokens.com/use-oss). -- Click on the "Binary" tab. -- Choose your database. -- Download the SuperTokens zip file for your OS. - -Once downloaded, extract the zip, and you will see a folder named `supertokens`. - -## 2) Install SuperTokens - - - - -```bash -# sudo is required so that the supertokens -# command can be added to your PATH variable. - -cd supertokens -sudo ./install -``` - - - - -```bash - -cd supertokens -./install - -``` -:::caution -You may get an error like `java cannot be opened because the developer cannot be verified`. To solve this, visit System Preferences > Security & Privacy > General Tab, and then click on the Allow button at the bottom. Then retry the command above. -::: - - - - - -```batch - -Rem run as an Administrator. This is required so that the supertokens -Rem command can be added to your PATH. - -cd supertokens -install.bat - -``` - - - -:::important -After installing, you can delete the downloaded folder as you no longer need it. - -Any changes to the the config will be done in the `config.yaml` file in the installation directory, the location of which is specified in the output of the `supertokens --help` command. -::: - -## 3) Start SuperTokens 🚀 -Running the following command will start the service. -```bash -supertokens start [--host=...] [--port=...] -``` -- The above command will start the container with an in-memory database. -- When you are ready to connect it to your database, please visit the [Database setup section](../database-setup/mysql) -- To see all available options please run `supertokens start --help` - - -## 4) Testing that the service is running 🤞 -Open a browser and visit `http://localhost:3567/hello`. If you see a page that says `Hello` back, then SuperTokens was started successfully! - -If you are having issues with starting the docker image, please feel free to reach out to us [over email](mailto:team@supertokens.com) or [via Discord](https://supertokens.com/discord). - - -## 5) Stopping SuperTokens 🛑 -```bash -supertokens stop -``` - -## Connecting the backend SDK with SuperTokens 🔌 -- The default `host` and `port` for SuperTokens is `localhost:3567`. You can change this by passing `--host` and `--port` options to the `start` command. -- The connection info will go in the `supertokens` object in the `init` function on your backend: - - - - - -```tsx -import supertokens from "supertokens-node"; - -supertokens.init({ - // highlight-start - supertokens: { - connectionURI: "http://localhost:3567", - apiKey: "someKey" // OR can be undefined - }, - // highlight-end - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "..." - }, - recipeList: [] -}); - -``` - - - - -```go -import "github.com/supertokens/supertokens-golang/supertokens" - -func main() { - supertokens.Init(supertokens.TypeInput{ - // highlight-start - Supertokens: &supertokens.ConnectionInfo{ - ConnectionURI: "http://localhost:3567", - APIKey: "someKey", - }, - // highlight-end - }) -} - -``` - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig - -init( - app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), - # highlight-start - supertokens_config=SupertokensConfig( - connection_uri='http://localhost:3567', - api_key='someKey' - ), - # highlight-end - framework='...', # type: ignore - recipe_list=[ - #... - ] -) -``` - - - - -:::tip Security -There is no API key by default. Visit the "Auth flow customization" -> "SuperTokens core settings" -> "Adding API Keys" section to see how to add one. -::: diff --git a/v2/passwordless/custom-ui/init/database-setup/mysql.mdx b/v2/passwordless/custom-ui/init/database-setup/mysql.mdx index eb37456b2..3597e3e64 100644 --- a/v2/passwordless/custom-ui/init/database-setup/mysql.mdx +++ b/v2/passwordless/custom-ui/init/database-setup/mysql.mdx @@ -4,533 +4,7 @@ title: If using MySQL hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import SelfHostedTabs from "/src/components/tabs/SelfHostedTabs" -import TabItem from '@theme/TabItem'; + -# MySQL setup - -:::important -This is needed only if you are running the SuperTokens core yourself. - -The minimum required version is **MySQL 5.7**. -::: - -:::tip blog -We also have [a blog post writeup](https://supertokens.com/blog/connect-supertokens-to-database) highlighting all the steps in more detail for different scenarios. -::: - -## 1) Create a database 🛠️ -```sql -CREATE DATABASE supertokens; -``` -You can skip this step if you want SuperTokens to write to your own database. In this case, you will need to provide your database's name as shown in the step below. - -## 2) Connect SuperTokens to your database 🔌 - - - - -:::caution -Host being `localhost` / `127.0.0.1` will not work in a docker image. Instead, please provide the database's local / public hostname or IP address. - -You also need to make the database listen on all the IP's of the local machine. -This can be done by editing the `mysqld.cnf` config file and setting the value of `bind-address` to `0.0.0.0`. -::: - -```bash - -docker run \ - -p 3567:3567 \ -//highlight-next-line - -e MYSQL_CONNECTION_URI="mysql://username:pass@host/dbName" \ - -d registry.supertokens.io/supertokens/supertokens-mysql - -# OR - -docker run \ - -p 3567:3567 \ -//highlight-start - -e MYSQL_USER="username" \ - -e MYSQL_PASSWORD="password" \ - -e MYSQL_HOST="host" \ - -e MYSQL_PORT="3306" \ - -e MYSQL_DATABASE_NAME="supertokens" \ -//highlight-end - -d registry.supertokens.io/supertokens/supertokens-mysql -``` - - - - - - - -```yaml -# You need to add the following to the config.yaml file. -# The file path can be found by running the "supertokens --help" command - -mysql_connection_uri: "mysql://username:pass@host/dbName" - -# OR - -mysql_user: "username" - -mysql_password: "password" - -mysql_host: "host" - -mysql_port: 3306 - -mysql_database_name: "supertokens" -``` - - - - -## 3) Create tables 👩‍💻👨‍💻 - -:::note -This happens automatically, unless you provide a MySQL user that doesn't have table creation permission. -::: - -```sql -CREATE TABLE `apps` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `created_at_time` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`) -); - -CREATE TABLE `tenants` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `created_at_time` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `tenant_configs` ( - `connection_uri_domain` varchar(256) NOT NULL DEFAULT '', - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `core_config` text, - `email_password_enabled` tinyint(1) DEFAULT NULL, - `passwordless_enabled` tinyint(1) DEFAULT NULL, - `third_party_enabled` tinyint(1) DEFAULT NULL, - `is_first_factors_null` tinyint(1) DEFAULT NULL, - PRIMARY KEY (`connection_uri_domain`,`app_id`,`tenant_id`) -); - -CREATE TABLE `tenant_thirdparty_providers` ( - `connection_uri_domain` varchar(256) NOT NULL DEFAULT '', - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `third_party_id` varchar(28) NOT NULL, - `name` varchar(64) DEFAULT NULL, - `authorization_endpoint` text, - `authorization_endpoint_query_params` text, - `token_endpoint` text, - `token_endpoint_body_params` text, - `user_info_endpoint` text, - `user_info_endpoint_query_params` text, - `user_info_endpoint_headers` text, - `jwks_uri` text, - `oidc_discovery_endpoint` text, - `require_email` tinyint(1) DEFAULT NULL, - `user_info_map_from_id_token_payload_user_id` varchar(64) DEFAULT NULL, - `user_info_map_from_id_token_payload_email` varchar(64) DEFAULT NULL, - `user_info_map_from_id_token_payload_email_verified` varchar(64) DEFAULT NULL, - `user_info_map_from_user_info_endpoint_user_id` varchar(64) DEFAULT NULL, - `user_info_map_from_user_info_endpoint_email` varchar(64) DEFAULT NULL, - `user_info_map_from_user_info_endpoint_email_verified` varchar(64) DEFAULT NULL, - PRIMARY KEY (`connection_uri_domain`,`app_id`,`tenant_id`,`third_party_id`), - FOREIGN KEY (`connection_uri_domain`, `app_id`, `tenant_id`) REFERENCES `tenant_configs` (`connection_uri_domain`, `app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE TABLE `tenant_thirdparty_provider_clients` ( - `connection_uri_domain` varchar(256) NOT NULL DEFAULT '', - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `third_party_id` varchar(28) NOT NULL, - `client_type` varchar(64) NOT NULL DEFAULT '', - `client_id` varchar(256) NOT NULL, - `client_secret` text, - `scope` text, - `force_pkce` tinyint(1) DEFAULT NULL, - `additional_config` text, - PRIMARY KEY (`connection_uri_domain`,`app_id`,`tenant_id`,`third_party_id`,`client_type`), - FOREIGN KEY (`connection_uri_domain`, `app_id`, `tenant_id`, `third_party_id`) REFERENCES `tenant_thirdparty_providers` (`connection_uri_domain`, `app_id`, `tenant_id`, `third_party_id`) ON DELETE CASCADE -); - -CREATE TABLE `tenant_first_factors` ( - connection_uri_domain VARCHAR(256) DEFAULT '', - app_id VARCHAR(64) DEFAULT 'public', - tenant_id VARCHAR(64) DEFAULT 'public', - factor_id VARCHAR(128), - PRIMARY KEY (`connection_uri_domain`, `app_id`, `tenant_id`, `factor_id`), - FOREIGN KEY (`connection_uri_domain`, `app_id`, `tenant_id`) REFERENCES `tenant_configs` (`connection_uri_domain`, `app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE TABLE `tenant_required_secondary_factors` ( - connection_uri_domain VARCHAR(256) DEFAULT '', - app_id VARCHAR(64) DEFAULT 'public', - tenant_id VARCHAR(64) DEFAULT 'public', - factor_id VARCHAR(128), - PRIMARY KEY (`connection_uri_domain`, `app_id`, `tenant_id`, `factor_id`), - FOREIGN KEY (`connection_uri_domain`, `app_id`, `tenant_id`) REFERENCES `tenant_configs` (`connection_uri_domain`, `app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE TABLE `key_value` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `name` varchar(128) NOT NULL, - `value` text, - `created_at_time` bigint unsigned DEFAULT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`name`), - FOREIGN KEY (`app_id`, `tenant_id`) REFERENCES `tenants` (`app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE TABLE `app_id_to_user_id` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `recipe_id` varchar(128) NOT NULL, - `primary_or_recipe_user_id` char(36) NOT NULL, - `is_linked_or_is_a_primary_user` BOOLEAN NOT NULL DEFAULT FALSE, - PRIMARY KEY (`app_id`,`user_id`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE, - FOREIGN KEY (`app_id`, `primary_or_recipe_user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE INDEX app_id_to_user_id_primary_user_id_index ON `app_id_to_user_id` (`primary_or_recipe_user_id`); - -CREATE INDEX app_id_to_user_id_user_id_index ON `app_id_to_user_id` (`user_id`); - -CREATE TABLE `all_auth_recipe_users` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `primary_or_recipe_user_id` char(36) NOT NULL, - `is_linked_or_is_a_primary_user` BOOLEAN NOT NULL DEFAULT FALSE, - `recipe_id` varchar(128) NOT NULL, - `time_joined` bigint unsigned NOT NULL, - `primary_or_recipe_user_time_joined` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`user_id`), - KEY `app_id` (`app_id`,`user_id`), - FOREIGN KEY (`app_id`, `tenant_id`) REFERENCES `tenants` (`app_id`, `tenant_id`) ON DELETE CASCADE, - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE, - FOREIGN KEY (`app_id`, `primary_or_recipe_user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE INDEX all_auth_recipe_users_pagination_index1 ON all_auth_recipe_users - (app_id, tenant_id, primary_or_recipe_user_time_joined DESC, primary_or_recipe_user_id DESC); - -CREATE INDEX all_auth_recipe_users_pagination_index3 ON all_auth_recipe_users - (recipe_id, app_id, tenant_id, primary_or_recipe_user_time_joined DESC, primary_or_recipe_user_id DESC); - -CREATE INDEX all_auth_recipe_users_primary_user_id_index ON all_auth_recipe_users - (primary_or_recipe_user_id, app_id); - -CREATE INDEX all_auth_recipe_users_recipe_id_index ON all_auth_recipe_users - (app_id, recipe_id, tenant_id); - -CREATE TABLE `userid_mapping` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `supertokens_user_id` char(36) NOT NULL, - `external_user_id` varchar(128) NOT NULL, - `external_user_id_info` text, - PRIMARY KEY (`app_id`,`supertokens_user_id`,`external_user_id`), - UNIQUE KEY `supertokens_user_id` (`app_id`,`supertokens_user_id`), - UNIQUE KEY `external_user_id` (`app_id`,`external_user_id`), - FOREIGN KEY (`app_id`, `supertokens_user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE TABLE `dashboard_users` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `email` varchar(256) NOT NULL, - `password_hash` varchar(256) NOT NULL, - `time_joined` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`user_id`), - UNIQUE KEY `email` (`app_id`,`email`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `dashboard_user_sessions` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `session_id` char(36) NOT NULL, - `user_id` char(36) NOT NULL, - `time_created` bigint unsigned NOT NULL, - `expiry` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`session_id`), - KEY `app_id` (`app_id`,`user_id`), - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `dashboard_users` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE INDEX `dashboard_user_sessions_expiry_index` ON `dashboard_user_sessions` (`expiry`); - -CREATE TABLE `session_access_token_signing_keys` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `created_at_time` bigint unsigned NOT NULL, - `value` text, - PRIMARY KEY (`app_id`,`created_at_time`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `session_info` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `session_handle` varchar(255) NOT NULL, - `user_id` varchar(128) NOT NULL, - `refresh_token_hash_2` varchar(128) NOT NULL, - `session_data` text, - `expires_at` bigint unsigned NOT NULL, - `created_at_time` bigint unsigned NOT NULL, - `jwt_user_payload` text, - `use_static_key` tinyint(1) NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`session_handle`), - FOREIGN KEY (`app_id`, `tenant_id`) REFERENCES `tenants` (`app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE INDEX `session_expiry_index` ON `session_info` (`expires_at`); - -CREATE TABLE `user_last_active` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - `last_active_time` bigint unsigned DEFAULT NULL, - PRIMARY KEY (`app_id`,`user_id`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `emailpassword_users` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `email` varchar(256) NOT NULL, - `password_hash` varchar(256) NOT NULL, - `time_joined` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`user_id`), - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE TABLE `emailpassword_user_to_tenant` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `email` varchar(256) NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`user_id`), - UNIQUE KEY `email` (`app_id`,`tenant_id`,`email`), - FOREIGN KEY (`app_id`, `tenant_id`, `user_id`) REFERENCES `all_auth_recipe_users` (`app_id`, `tenant_id`, `user_id`) ON DELETE CASCADE -); - -CREATE TABLE `emailpassword_pswd_reset_tokens` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `token` varchar(128) NOT NULL, - `token_expiry` bigint unsigned NOT NULL, - `email` varchar(256), - PRIMARY KEY (`app_id`,`user_id`,`token`), - UNIQUE KEY `token` (`token`), - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE ON UPDATE CASCADE -); - -CREATE INDEX `emailpassword_password_reset_token_expiry_index` ON `emailpassword_pswd_reset_tokens` (`token_expiry`); - -CREATE TABLE `emailverification_verified_emails` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - `email` varchar(256) NOT NULL, - PRIMARY KEY (`app_id`,`user_id`,`email`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `emailverification_tokens` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - `email` varchar(256) NOT NULL, - `token` varchar(128) NOT NULL, - `token_expiry` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`user_id`,`email`,`token`), - UNIQUE KEY `token` (`token`), - FOREIGN KEY (`app_id`, `tenant_id`) REFERENCES `tenants` (`app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE INDEX `emailverification_tokens_index` ON `emailverification_tokens` (`token_expiry`); - -CREATE TABLE `thirdparty_users` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `third_party_id` varchar(28) NOT NULL, - `third_party_user_id` varchar(256) NOT NULL, - `user_id` char(36) NOT NULL, - `email` varchar(256) NOT NULL, - `time_joined` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`user_id`), - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE INDEX `thirdparty_users_email_index` ON `thirdparty_users` (`app_id`,`email`); - -CREATE INDEX `thirdparty_users_thirdparty_user_id_index` ON `thirdparty_users` (`app_id`,`third_party_id`,`third_party_user_id`); - -CREATE TABLE `thirdparty_user_to_tenant` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `third_party_id` varchar(28) NOT NULL, - `third_party_user_id` varchar(256) NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`user_id`), - UNIQUE KEY `third_party_user_id` (`app_id`,`tenant_id`,`third_party_id`,`third_party_user_id`), - FOREIGN KEY (`app_id`, `tenant_id`, `user_id`) REFERENCES `all_auth_recipe_users` (`app_id`, `tenant_id`, `user_id`) ON DELETE CASCADE -); - -CREATE TABLE `passwordless_users` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `email` varchar(256) DEFAULT NULL, - `phone_number` varchar(256) DEFAULT NULL, - `time_joined` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`user_id`), - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE TABLE `passwordless_user_to_tenant` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `email` varchar(256) DEFAULT NULL, - `phone_number` varchar(256) DEFAULT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`user_id`), - UNIQUE KEY `email` (`app_id`,`tenant_id`,`email`), - UNIQUE KEY `phone_number` (`app_id`,`tenant_id`,`phone_number`), - FOREIGN KEY (`app_id`, `tenant_id`, `user_id`) REFERENCES `all_auth_recipe_users` (`app_id`, `tenant_id`, `user_id`) ON DELETE CASCADE -); - -CREATE TABLE `passwordless_devices` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `device_id_hash` char(44) NOT NULL, - `email` varchar(256) DEFAULT NULL, - `phone_number` varchar(256) DEFAULT NULL, - `link_code_salt` char(44) NOT NULL, - `failed_attempts` int unsigned NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`device_id_hash`), - FOREIGN KEY (`app_id`, `tenant_id`) REFERENCES `tenants` (`app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE INDEX `passwordless_devices_email_index` ON `passwordless_devices` (`app_id`,`tenant_id`,`email`); - -CREATE INDEX `passwordless_devices_phone_number_index` ON `passwordless_devices` (`app_id`,`tenant_id`,`phone_number`); - -CREATE TABLE `passwordless_codes` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `code_id` char(36) NOT NULL, - `device_id_hash` char(44) NOT NULL, - `link_code_hash` char(44) NOT NULL, - `created_at` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`code_id`), - UNIQUE KEY `link_code_hash` (`app_id`,`tenant_id`,`link_code_hash`), - KEY `app_id` (`app_id`,`tenant_id`,`device_id_hash`), - FOREIGN KEY (`app_id`, `tenant_id`, `device_id_hash`) REFERENCES `passwordless_devices` (`app_id`, `tenant_id`, `device_id_hash`) ON DELETE CASCADE ON UPDATE CASCADE -); - -CREATE INDEX `passwordless_codes_created_at_index` ON `passwordless_codes` (`app_id`,`tenant_id`,`created_at`); - -CREATE TABLE `jwt_signing_keys` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `key_id` varchar(255) NOT NULL, - `key_string` text NOT NULL, - `algorithm` varchar(10) NOT NULL, - `created_at` bigint unsigned DEFAULT NULL, - PRIMARY KEY (`app_id`,`key_id`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `user_metadata` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - `user_metadata` text NOT NULL, - PRIMARY KEY (`app_id`,`user_id`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `roles` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `role` varchar(255) NOT NULL, - PRIMARY KEY (`app_id`,`role`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `role_permissions` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `role` varchar(255) NOT NULL, - `permission` varchar(255) NOT NULL, - PRIMARY KEY (`app_id`,`role`,`permission`), - FOREIGN KEY (`app_id`, `role`) REFERENCES `roles` (`app_id`, `role`) ON DELETE CASCADE -); - -CREATE INDEX `role_permissions_permission_index` ON `role_permissions` (`app_id`,`permission`); - -CREATE TABLE `user_roles` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - `role` varchar(255) NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`user_id`,`role`), - FOREIGN KEY (`app_id`, `tenant_id`) REFERENCES `tenants` (`app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE INDEX `user_roles_role_index` ON `user_roles` (`app_id`,`tenant_id`,`role`); - -CREATE TABLE `totp_users` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - PRIMARY KEY (`app_id`,`user_id`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `totp_user_devices` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - `device_name` varchar(256) NOT NULL, - `secret_key` varchar(256) NOT NULL, - `period` int NOT NULL, - `skew` int NOT NULL, - `verified` tinyint(1) NOT NULL, - `created_at` BIGINT UNSIGNED, - PRIMARY KEY (`app_id`,`user_id`,`device_name`), - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `totp_users` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE TABLE `totp_used_codes` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - `code` varchar(8) NOT NULL, - `is_valid` tinyint(1) NOT NULL, - `expiry_time_ms` bigint unsigned NOT NULL, - `created_time_ms` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`user_id`,`created_time_ms`), - KEY `app_id` (`app_id`,`user_id`), - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `totp_users` (`app_id`, `user_id`) ON DELETE CASCADE, - FOREIGN KEY (`app_id`, `tenant_id`) REFERENCES `tenants` (`app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE INDEX `totp_used_codes_expiry_time_ms_index` ON `totp_used_codes` (`app_id`,`tenant_id`,`expiry_time_ms`); -``` - -:::tip -You also have the option to [rename these tables](./rename-database-tables). -::: - -## 4) Test the connection 🤞 -To test, start SuperTokens and run the following query in your database -```sql -SELECT * FROM key_value; -``` -If you see at least one row, it means that the connection has been successfully completed! 🥳🎉 - -:::tip blog -We also have [a blog post writeup](https://supertokens.com/blog/connect-supertokens-to-database) highlighting all the steps in more detail for different scenarios. -::: diff --git a/v2/passwordless/custom-ui/init/database-setup/postgresql.mdx b/v2/passwordless/custom-ui/init/database-setup/postgresql.mdx index abaea10fc..8375aa15f 100644 --- a/v2/passwordless/custom-ui/init/database-setup/postgresql.mdx +++ b/v2/passwordless/custom-ui/init/database-setup/postgresql.mdx @@ -4,601 +4,8 @@ title: If using PostgreSQL hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import SelfHostedTabs from "/src/components/tabs/SelfHostedTabs" -import TabItem from '@theme/TabItem'; -# PostgreSQL setup + -:::important -This is needed only if you are running the SuperTokens core yourself. - -The minimum required version is **PostgreSQL 9.6**. -::: - -:::tip blog -We also have [a blog post writeup](https://supertokens.com/blog/connect-supertokens-to-database) highlighting all the steps in more detail for different scenarios. -::: - -## 1) Create a database 🛠️ - -```sql -CREATE DATABASE supertokens; -``` -You can skip this step if you want SuperTokens to write to your own database. In this case, you will need to provide your database name as shown in the step below. - -## 2) Connect SuperTokens to your database 🔌 - - - - -:::caution -Host being `localhost` / `127.0.0.1` will not work in a docker image. Instead, please provide the database's local / public hostname or IP address. - -You also need to make the database listen on all the IP's of the local machine. -This can be done by editing the `postgresql.conf` config file and setting the value of `listen_addresses` to `0.0.0.0`. -::: - -```bash - -docker run \ - -p 3567:3567 \ - // highlight-next-line - -e POSTGRESQL_CONNECTION_URI="postgresql://username:pass@host/dbName" \ - -d registry.supertokens.io/supertokens/supertokens-postgresql - -# OR - -docker run \ - -p 3567:3567 \ - // highlight-start - -e POSTGRESQL_USER="username" \ - -e POSTGRESQL_PASSWORD="password" \ - -e POSTGRESQL_HOST="host" \ - -e POSTGRESQL_PORT="5432" \ - -e POSTGRESQL_DATABASE_NAME="supertokens" \ - // highlight-end - -d registry.supertokens.io/supertokens/supertokens-postgresql -``` - -:::tip -You can also provide the table schema by providing the `POSTGRESQL_TABLE_SCHEMA` option. -::: - - - - - -```yaml -# You need to add the following to the config.yaml file. -# The file path can be found by running the "supertokens --help" command - -postgresql_connection_uri: "postgresql://username:pass@host/dbName" - -# OR - -postgresql_user: "username" - -postgresql_password: "password" - -postgresql_host: "host" - -postgresql_port: "5432" - -postgresql_database_name: "supertokens" -``` - -- You can also provide the table schema by providing the `postgresql_table_schema` option. - - - - - -## 3) Create tables 👩‍💻👨‍💻 - -:::note -This happens automatically, unless you provide a PostgreSQL user that doesn't have table creation permission. -::: - -```sql -CREATE TABLE apps ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - created_at_time BIGINT, - CONSTRAINT apps_pkey PRIMARY KEY (app_id) -); - -CREATE TABLE tenants ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - created_at_time BIGINT, - CONSTRAINT tenants_pkey PRIMARY KEY (app_id, tenant_id), - CONSTRAINT tenants_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX tenants_app_id_index ON tenants (app_id); - -CREATE TABLE tenant_configs ( - connection_uri_domain VARCHAR(256) DEFAULT '' NOT NULL, - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - core_config TEXT, - email_password_enabled BOOLEAN, - passwordless_enabled BOOLEAN, - third_party_enabled BOOLEAN, - is_first_factors_null BOOLEAN, - CONSTRAINT tenant_configs_pkey PRIMARY KEY (connection_uri_domain, app_id, tenant_id) -); - -CREATE TABLE tenant_thirdparty_providers ( - connection_uri_domain VARCHAR(256) DEFAULT '' NOT NULL, - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - third_party_id VARCHAR(28) NOT NULL, - name VARCHAR(64), - authorization_endpoint TEXT, - authorization_endpoint_query_params TEXT, - token_endpoint TEXT, - token_endpoint_body_params TEXT, - user_info_endpoint TEXT, - user_info_endpoint_query_params TEXT, - user_info_endpoint_headers TEXT, - jwks_uri TEXT, - oidc_discovery_endpoint TEXT, - require_email BOOLEAN, - user_info_map_from_id_token_payload_user_id VARCHAR(64), - user_info_map_from_id_token_payload_email VARCHAR(64), - user_info_map_from_id_token_payload_email_verified VARCHAR(64), - user_info_map_from_user_info_endpoint_user_id VARCHAR(64), - user_info_map_from_user_info_endpoint_email VARCHAR(64), - user_info_map_from_user_info_endpoint_email_verified VARCHAR(64), - CONSTRAINT tenant_thirdparty_providers_pkey PRIMARY KEY (connection_uri_domain, app_id, tenant_id, third_party_id), - CONSTRAINT tenant_thirdparty_providers_tenant_id_fkey FOREIGN KEY (connection_uri_domain, app_id, tenant_id) REFERENCES public.tenant_configs(connection_uri_domain, app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX tenant_thirdparty_providers_tenant_id_index ON tenant_thirdparty_providers (connection_uri_domain, app_id, tenant_id); - -CREATE TABLE tenant_thirdparty_provider_clients ( - connection_uri_domain VARCHAR(256) DEFAULT '' NOT NULL, - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - third_party_id VARCHAR(28) NOT NULL, - client_type VARCHAR(64) DEFAULT '' NOT NULL, - client_id VARCHAR(256) NOT NULL, - client_secret TEXT, - scope VARCHAR(128)[], - force_pkce BOOLEAN, - additional_config TEXT, - CONSTRAINT tenant_thirdparty_provider_clients_pkey PRIMARY KEY (connection_uri_domain, app_id, tenant_id, third_party_id, client_type), - CONSTRAINT tenant_thirdparty_provider_clients_third_party_id_fkey FOREIGN KEY (connection_uri_domain, app_id, tenant_id, third_party_id) REFERENCES public.tenant_thirdparty_providers(connection_uri_domain, app_id, tenant_id, third_party_id) ON DELETE CASCADE -); - -CREATE INDEX tenant_thirdparty_provider_clients_third_party_id_index ON tenant_thirdparty_provider_clients (connection_uri_domain, app_id, tenant_id, third_party_id); - -CREATE TABLE tenant_first_factors ( - connection_uri_domain VARCHAR(256) DEFAULT '' NOT NULL, - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - factor_id VARCHAR(128), - CONSTRAINT tenant_first_factors_pkey PRIMARY KEY (connection_uri_domain, app_id, tenant_id, factor_id), - CONSTRAINT tenant_first_factors_tenant_id_fkey FOREIGN KEY (connection_uri_domain, app_id, tenant_id) REFERENCES public.tenant_configs(connection_uri_domain, app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX IF NOT EXISTS tenant_first_factors_tenant_id_index ON tenant_first_factors (connection_uri_domain, app_id, tenant_id); - -CREATE TABLE tenant_required_secondary_factors ( - connection_uri_domain VARCHAR(256) DEFAULT '' NOT NULL, - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - factor_id VARCHAR(128), - CONSTRAINT tenant_required_secondary_factors_pkey PRIMARY KEY (connection_uri_domain, app_id, tenant_id, factor_id), - CONSTRAINT tenant_required_secondary_factors_tenant_id_fkey FOREIGN KEY (connection_uri_domain, app_id, tenant_id) REFERENCES public.tenant_configs(connection_uri_domain, app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX IF NOT EXISTS tenant_default_required_factor_ids_tenant_id_index ON tenant_required_secondary_factors (connection_uri_domain, app_id, tenant_id); - -CREATE TABLE key_value ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - name VARCHAR(128) NOT NULL, - value TEXT, - created_at_time BIGINT, - CONSTRAINT key_value_pkey PRIMARY KEY (app_id, tenant_id, name), - CONSTRAINT key_value_tenant_id_fkey FOREIGN KEY (app_id, tenant_id) REFERENCES public.tenants(app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX key_value_tenant_id_index ON key_value (app_id, tenant_id); - -CREATE TABLE app_id_to_user_id ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - recipe_id VARCHAR(128) NOT NULL, - primary_or_recipe_user_id CHAR(36) NOT NULL, - is_linked_or_is_a_primary_user BOOLEAN NOT NULL DEFAULT FALSE, - CONSTRAINT app_id_to_user_id_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT app_id_to_user_id_primary_or_recipe_user_id_fkey FOREIGN KEY(app_id, primary_or_recipe_user_id) REFERENCES app_id_to_user_id (app_id, user_id) ON DELETE CASCADE, - CONSTRAINT app_id_to_user_id_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX app_id_to_user_id_app_id_index ON app_id_to_user_id (app_id); - -CREATE INDEX app_id_to_user_id_primary_user_id_index ON app_id_to_user_id (primary_or_recipe_user_id, app_id); - -CREATE TABLE all_auth_recipe_users ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - primary_or_recipe_user_id CHAR(36) NOT NULL, - is_linked_or_is_a_primary_user BOOLEAN NOT NULL DEFAULT FALSE, - recipe_id VARCHAR(128) NOT NULL, - time_joined BIGINT NOT NULL, - primary_or_recipe_user_time_joined BIGINT NOT NULL, - CONSTRAINT all_auth_recipe_users_pkey PRIMARY KEY (app_id, tenant_id, user_id), - CONSTRAINT all_auth_recipe_users_tenant_id_fkey FOREIGN KEY (app_id, tenant_id) REFERENCES public.tenants(app_id, tenant_id) ON DELETE CASCADE, - CONSTRAINT all_auth_recipe_users_primary_or_recipe_user_id_fkey FOREIGN KEY(app_id, primary_or_recipe_user_id) REFERENCES public.app_id_to_user_id (app_id, user_id) ON DELETE CASCADE, - CONSTRAINT all_auth_recipe_users_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.app_id_to_user_id(app_id, user_id) ON DELETE CASCADE -); - -CREATE INDEX all_auth_recipe_users_pagination_index1 ON all_auth_recipe_users - (app_id, tenant_id, primary_or_recipe_user_time_joined DESC, primary_or_recipe_user_id DESC); - -CREATE INDEX all_auth_recipe_users_pagination_index2 ON all_auth_recipe_users - (app_id, tenant_id, primary_or_recipe_user_time_joined ASC, primary_or_recipe_user_id DESC); - -CREATE INDEX all_auth_recipe_users_pagination_index3 ON all_auth_recipe_users - (recipe_id, app_id, tenant_id, primary_or_recipe_user_time_joined DESC, primary_or_recipe_user_id DESC); - - -CREATE INDEX all_auth_recipe_users_pagination_index4 ON all_auth_recipe_users - (recipe_id, app_id, tenant_id, primary_or_recipe_user_time_joined ASC, primary_or_recipe_user_id DESC); - - -CREATE INDEX all_auth_recipe_users_primary_user_id_index ON all_auth_recipe_users - (primary_or_recipe_user_id, app_id); - -CREATE INDEX all_auth_recipe_users_recipe_id_index ON all_auth_recipe_users - (app_id, recipe_id, tenant_id); - -CREATE INDEX all_auth_recipe_user_id_index ON all_auth_recipe_users (app_id, user_id); - -CREATE INDEX all_auth_recipe_tenant_id_index ON all_auth_recipe_users (app_id, tenant_id); - -CREATE TABLE userid_mapping ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - supertokens_user_id character(36) NOT NULL, - external_user_id VARCHAR(128) NOT NULL, - external_user_id_info TEXT, - CONSTRAINT userid_mapping_external_user_id_key UNIQUE (app_id, external_user_id), - CONSTRAINT userid_mapping_pkey PRIMARY KEY (app_id, supertokens_user_id, external_user_id), - CONSTRAINT userid_mapping_supertokens_user_id_key UNIQUE (app_id, supertokens_user_id), - CONSTRAINT userid_mapping_supertokens_user_id_fkey FOREIGN KEY (app_id, supertokens_user_id) REFERENCES public.app_id_to_user_id(app_id, user_id) ON DELETE CASCADE -); - -CREATE INDEX userid_mapping_supertokens_user_id_index ON userid_mapping (app_id, supertokens_user_id); - -CREATE TABLE dashboard_users ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - email VARCHAR(256) NOT NULL, - password_hash VARCHAR(256) NOT NULL, - time_joined BIGINT NOT NULL, - CONSTRAINT dashboard_users_email_key UNIQUE (app_id, email), - CONSTRAINT dashboard_users_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT dashboard_users_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX dashboard_users_app_id_index ON dashboard_users (app_id); - -CREATE TABLE dashboard_user_sessions ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - session_id character(36) NOT NULL, - user_id character(36) NOT NULL, - time_created BIGINT NOT NULL, - expiry BIGINT NOT NULL, - CONSTRAINT dashboard_user_sessions_pkey PRIMARY KEY (app_id, session_id), - CONSTRAINT dashboard_user_sessions_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.dashboard_users(app_id, user_id) ON UPDATE CASCADE ON DELETE CASCADE -); - -CREATE INDEX dashboard_user_sessions_expiry_index ON dashboard_user_sessions (expiry); - -CREATE INDEX dashboard_user_sessions_user_id_index ON dashboard_user_sessions (app_id, user_id); - -CREATE TABLE session_access_token_signing_keys ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - created_at_time BIGINT NOT NULL, - value TEXT, - CONSTRAINT session_access_token_signing_keys_pkey PRIMARY KEY (app_id, created_at_time), - CONSTRAINT session_access_token_signing_keys_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX access_token_signing_keys_app_id_index ON session_access_token_signing_keys (app_id); - -CREATE TABLE session_info ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - session_handle VARCHAR(255) NOT NULL, - user_id VARCHAR(128) NOT NULL, - refresh_token_hash_2 VARCHAR(128) NOT NULL, - session_data TEXT, - expires_at BIGINT NOT NULL, - created_at_time BIGINT NOT NULL, - jwt_user_payload TEXT, - use_static_key BOOLEAN NOT NULL, - CONSTRAINT session_info_pkey PRIMARY KEY (app_id, tenant_id, session_handle), - CONSTRAINT session_info_tenant_id_fkey FOREIGN KEY (app_id, tenant_id) REFERENCES public.tenants(app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX session_expiry_index ON session_info (expires_at); - -CREATE INDEX session_info_tenant_id_index ON session_info (app_id, tenant_id); - -CREATE TABLE user_last_active ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - last_active_time BIGINT, - CONSTRAINT user_last_active_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT user_last_active_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX user_last_active_app_id_index ON user_last_active (app_id); - -CREATE TABLE emailpassword_users ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - email VARCHAR(256) NOT NULL, - password_hash VARCHAR(256) NOT NULL, - time_joined BIGINT NOT NULL, - CONSTRAINT emailpassword_users_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT emailpassword_users_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.app_id_to_user_id(app_id, user_id) ON DELETE CASCADE -); - -CREATE TABLE emailpassword_user_to_tenant ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - email VARCHAR(256) NOT NULL, - CONSTRAINT emailpassword_user_to_tenant_email_key UNIQUE (app_id, tenant_id, email), - CONSTRAINT emailpassword_user_to_tenant_pkey PRIMARY KEY (app_id, tenant_id, user_id), - CONSTRAINT emailpassword_user_to_tenant_user_id_fkey FOREIGN KEY (app_id, tenant_id, user_id) REFERENCES public.all_auth_recipe_users(app_id, tenant_id, user_id) ON DELETE CASCADE -); - -CREATE TABLE emailpassword_pswd_reset_tokens ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - token VARCHAR(128) NOT NULL, - token_expiry BIGINT NOT NULL, - email VARCHAR(256), - CONSTRAINT emailpassword_pswd_reset_tokens_pkey PRIMARY KEY (app_id, user_id, token), - CONSTRAINT emailpassword_pswd_reset_tokens_token_key UNIQUE (token), - CONSTRAINT emailpassword_pswd_reset_tokens_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.app_id_to_user_id(app_id, user_id) ON UPDATE CASCADE ON DELETE CASCADE -); - -CREATE INDEX emailpassword_password_reset_token_expiry_index ON emailpassword_pswd_reset_tokens (token_expiry); - -CREATE INDEX emailpassword_pswd_reset_tokens_user_id_index ON emailpassword_pswd_reset_tokens (app_id, user_id); - -CREATE TABLE emailverification_verified_emails ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - email VARCHAR(256) NOT NULL, - CONSTRAINT emailverification_verified_emails_pkey PRIMARY KEY (app_id, user_id, email), - CONSTRAINT emailverification_verified_emails_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX emailverification_verified_emails_app_id_index ON emailverification_verified_emails (app_id); - -CREATE TABLE emailverification_tokens ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - email VARCHAR(256) NOT NULL, - token VARCHAR(128) NOT NULL, - token_expiry BIGINT NOT NULL, - CONSTRAINT emailverification_tokens_pkey PRIMARY KEY (app_id, tenant_id, user_id, email, token), - CONSTRAINT emailverification_tokens_token_key UNIQUE (token), - CONSTRAINT emailverification_tokens_tenant_id_fkey FOREIGN KEY (app_id, tenant_id) REFERENCES public.tenants(app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX emailverification_tokens_index ON emailverification_tokens (token_expiry); - -CREATE INDEX emailverification_tokens_tenant_id_index ON emailverification_tokens (app_id, tenant_id); - -CREATE TABLE thirdparty_users ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - third_party_id VARCHAR(28) NOT NULL, - third_party_user_id VARCHAR(256) NOT NULL, - user_id character(36) NOT NULL, - email VARCHAR(256) NOT NULL, - time_joined BIGINT NOT NULL, - CONSTRAINT thirdparty_users_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT thirdparty_users_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.app_id_to_user_id(app_id, user_id) ON DELETE CASCADE -); - -CREATE INDEX thirdparty_users_email_index ON thirdparty_users (app_id, email); - -CREATE INDEX thirdparty_users_thirdparty_user_id_index ON thirdparty_users (app_id, third_party_id, third_party_user_id); - -CREATE TABLE thirdparty_user_to_tenant ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - third_party_id VARCHAR(28) NOT NULL, - third_party_user_id VARCHAR(256) NOT NULL, - CONSTRAINT thirdparty_user_to_tenant_pkey PRIMARY KEY (app_id, tenant_id, user_id), - CONSTRAINT thirdparty_user_to_tenant_third_party_user_id_key UNIQUE (app_id, tenant_id, third_party_id, third_party_user_id), - CONSTRAINT thirdparty_user_to_tenant_user_id_fkey FOREIGN KEY (app_id, tenant_id, user_id) REFERENCES public.all_auth_recipe_users(app_id, tenant_id, user_id) ON DELETE CASCADE -); - -CREATE TABLE passwordless_users ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - email VARCHAR(256), - phone_number VARCHAR(256), - time_joined BIGINT NOT NULL, - CONSTRAINT passwordless_users_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT passwordless_users_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.app_id_to_user_id(app_id, user_id) ON DELETE CASCADE -); - -CREATE TABLE passwordless_user_to_tenant ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - email VARCHAR(256), - phone_number VARCHAR(256), - CONSTRAINT passwordless_user_to_tenant_email_key UNIQUE (app_id, tenant_id, email), - CONSTRAINT passwordless_user_to_tenant_phone_number_key UNIQUE (app_id, tenant_id, phone_number), - CONSTRAINT passwordless_user_to_tenant_pkey PRIMARY KEY (app_id, tenant_id, user_id), - CONSTRAINT passwordless_user_to_tenant_user_id_fkey FOREIGN KEY (app_id, tenant_id, user_id) REFERENCES public.all_auth_recipe_users(app_id, tenant_id, user_id) ON DELETE CASCADE -); - -CREATE TABLE passwordless_devices ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - device_id_hash character(44) NOT NULL, - email VARCHAR(256), - phone_number VARCHAR(256), - link_code_salt character(44) NOT NULL, - failed_attempts integer NOT NULL, - CONSTRAINT passwordless_devices_pkey PRIMARY KEY (app_id, tenant_id, device_id_hash), - CONSTRAINT passwordless_devices_tenant_id_fkey FOREIGN KEY (app_id, tenant_id) REFERENCES public.tenants(app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX passwordless_devices_email_index ON passwordless_devices (app_id, tenant_id, email); - -CREATE INDEX passwordless_devices_phone_number_index ON passwordless_devices (app_id, tenant_id, phone_number); - -CREATE INDEX passwordless_devices_tenant_id_index ON passwordless_devices (app_id, tenant_id); - -CREATE TABLE passwordless_codes ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - code_id character(36) NOT NULL, - device_id_hash character(44) NOT NULL, - link_code_hash character(44) NOT NULL, - created_at BIGINT NOT NULL, - CONSTRAINT passwordless_codes_link_code_hash_key UNIQUE (app_id, tenant_id, link_code_hash), - CONSTRAINT passwordless_codes_pkey PRIMARY KEY (app_id, tenant_id, code_id), - CONSTRAINT passwordless_codes_device_id_hash_fkey FOREIGN KEY (app_id, tenant_id, device_id_hash) REFERENCES public.passwordless_devices(app_id, tenant_id, device_id_hash) ON UPDATE CASCADE ON DELETE CASCADE -); - -CREATE INDEX passwordless_codes_created_at_index ON passwordless_codes (app_id, tenant_id, created_at); - -CREATE INDEX passwordless_codes_device_id_hash_index ON passwordless_codes (app_id, tenant_id, device_id_hash); - -CREATE TABLE jwt_signing_keys ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - key_id VARCHAR(255) NOT NULL, - key_string TEXT NOT NULL, - algorithm VARCHAR(10) NOT NULL, - created_at BIGINT, - CONSTRAINT jwt_signing_keys_pkey PRIMARY KEY (app_id, key_id), - CONSTRAINT jwt_signing_keys_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX jwt_signing_keys_app_id_index ON jwt_signing_keys (app_id); - -CREATE TABLE user_metadata ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - user_metadata TEXT NOT NULL, - CONSTRAINT user_metadata_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT user_metadata_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX user_metadata_app_id_index ON user_metadata (app_id); - -CREATE TABLE roles ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - role VARCHAR(255) NOT NULL, - CONSTRAINT roles_pkey PRIMARY KEY (app_id, role), - CONSTRAINT roles_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX roles_app_id_index ON roles (app_id); - -CREATE TABLE role_permissions ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - role VARCHAR(255) NOT NULL, - permission VARCHAR(255) NOT NULL, - CONSTRAINT role_permissions_pkey PRIMARY KEY (app_id, role, permission), - CONSTRAINT role_permissions_role_fkey FOREIGN KEY (app_id, role) REFERENCES public.roles(app_id, role) ON DELETE CASCADE -); - -CREATE INDEX role_permissions_permission_index ON role_permissions (app_id, permission); - -CREATE INDEX role_permissions_role_index ON role_permissions (app_id, role); - -CREATE TABLE user_roles ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - role VARCHAR(255) NOT NULL, - CONSTRAINT user_roles_pkey PRIMARY KEY (app_id, tenant_id, user_id, role), - CONSTRAINT user_roles_tenant_id_fkey FOREIGN KEY (app_id, tenant_id) REFERENCES public.tenants(app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX user_roles_role_index ON user_roles (app_id, tenant_id, role); - -CREATE INDEX user_roles_tenant_id_index ON user_roles (app_id, tenant_id); - -CREATE INDEX user_roles_app_id_role_index ON user_roles (app_id, role); - -CREATE TABLE totp_users ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - CONSTRAINT totp_users_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT totp_users_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX totp_users_app_id_index ON totp_users (app_id); - -CREATE TABLE totp_user_devices ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - device_name VARCHAR(256) NOT NULL, - secret_key VARCHAR(256) NOT NULL, - period integer NOT NULL, - skew integer NOT NULL, - verified BOOLEAN NOT NULL, - created_at BIGINT, - CONSTRAINT totp_user_devices_pkey PRIMARY KEY (app_id, user_id, device_name), - CONSTRAINT totp_user_devices_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.totp_users(app_id, user_id) ON DELETE CASCADE -); - -CREATE INDEX totp_user_devices_user_id_index ON totp_user_devices (app_id, user_id); - -CREATE TABLE totp_used_codes ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - code VARCHAR(8) NOT NULL, - is_valid BOOLEAN NOT NULL, - expiry_time_ms BIGINT NOT NULL, - created_time_ms BIGINT NOT NULL, - CONSTRAINT totp_used_codes_pkey PRIMARY KEY (app_id, tenant_id, user_id, created_time_ms), - CONSTRAINT totp_used_codes_tenant_id_fkey FOREIGN KEY (app_id, tenant_id) REFERENCES public.tenants(app_id, tenant_id) ON DELETE CASCADE, - CONSTRAINT totp_used_codes_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.totp_users(app_id, user_id) ON DELETE CASCADE -); - -CREATE INDEX totp_used_codes_expiry_time_ms_index ON totp_used_codes (app_id, tenant_id, expiry_time_ms); - -CREATE INDEX totp_used_codes_tenant_id_index ON totp_used_codes (app_id, tenant_id); - -CREATE INDEX totp_used_codes_user_id_index ON totp_used_codes (app_id, user_id); -``` - -:::tip -You also have the option to [rename these tables](./rename-database-tables). -::: - -## 4) Test the connection 🤞 -To test, start SuperTokens and run the following query in your database -```sql -SELECT * FROM key_value; -``` -If you see at least one row, it means that the connection has been successfully completed! 🥳🎉 - -:::tip blog -We also have [a blog post writeup](https://supertokens.com/blog/connect-supertokens-to-database) highlighting all the steps in more detail for different scenarios. -::: diff --git a/v2/passwordless/custom-ui/init/database-setup/rename-database-tables.mdx b/v2/passwordless/custom-ui/init/database-setup/rename-database-tables.mdx index 93f84ccc6..e02f73ba3 100644 --- a/v2/passwordless/custom-ui/init/database-setup/rename-database-tables.mdx +++ b/v2/passwordless/custom-ui/init/database-setup/rename-database-tables.mdx @@ -4,95 +4,8 @@ title: Rename database tables hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import SelfHostedTabs from "/src/components/tabs/SelfHostedTabs" -import TabItem from '@theme/TabItem'; -# Rename database tables + -:::caution -If you already have tables created by SuperTokens, and then you rename them, SuperTokens will create new tables. So please be sure to migrate the data from the existing one to the new one. -::: - -You can add a prefix to all table names that are managed by SuperTokens. This way, all of them will be renamed in a way that have no clashes with your tables. - -For example, two tables created by SuperTokens are called `emailpassword_users` and `thirdparty_users`. If you add a prefix to them (something like `"my_prefix"`), then the tables will be renamed to `my_prefix_emailpassword_users` and `my_prefix_thirdparty_users`. - -## For MySQL - - - - -```bash -docker run \ - -p 3567:3567 \ - // highlight-next-line - -e MYSQL_TABLE_NAMES_PREFIX="my_prefix" \ - -d registry.supertokens.io/supertokens/supertokens-mysql -``` - - - - -```yaml -# You need to add the following to the config.yaml file. -# The file path can be found by running the "supertokens --help" command - -mysql_table_names_prefix: "my_prefix" -``` - - - -## For PostgreSQL - - - - -```bash -docker run \ - -p 3567:3567 \ - // highlight-next-line - -e POSTGRESQL_TABLE_NAMES_PREFIX="my_prefix" \ - -d registry.supertokens.io/supertokens/supertokens-postgresql -``` - - - - -```yaml -# You need to add the following to the config.yaml file. -# The file path can be found by running the "supertokens --help" command - -postgresql_table_names_prefix: "my_prefix" -``` - - - -## For MongoDB - - - - -```bash -docker run \ - -p 3567:3567 \ - // highlight-next-line - -e MONGODB_COLLECTION_NAMES_PREFIX="my_prefix" \ - -d registry.supertokens.io/supertokens/supertokens-mongodb -``` - - - - - -```yaml -# You need to add the following to the config.yaml file. -# The file path can be found by running the "supertokens --help" command - -mongodb_collection_names_prefix: "my_prefix" -``` - - - diff --git a/v2/passwordless/custom-ui/init/frontend.mdx b/v2/passwordless/custom-ui/init/frontend.mdx index f9100de64..bac6ce88b 100644 --- a/v2/passwordless/custom-ui/init/frontend.mdx +++ b/v2/passwordless/custom-ui/init/frontend.mdx @@ -3,319 +3,9 @@ id: frontend title: "Step 1: Frontend" hide_title: true --- -import { PasswordlessFrontendForm } from "/src/components/snippetConfigForm/passwordlessFrontendForm"; +import Redirector from '/src/components/Redirector'; -import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" -import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" -import TabItem from '@theme/TabItem'; -import {Question, Answer}from "/src/components/question" -import AppInfoForm from "/src/components/appInfoForm" -import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" -import WebJsInjector from "/src/components/webJsInjector" + -# Frontend Integration - -## Supported frameworks - -import TechStackSupport from "../../../community/reusableMD/supported-tech-stacks-frontend-custom-ui.mdx" - - - -## 1) Install - - - - - - - -```bash -npm i -s supertokens-web-js -``` - - - - -You need to add all of the following scripts to your app - - - -```bash - - - - -``` - - - - - - - - - - -:::info - -If you want to implement a common authencation experience for both web and mobile, please look at our [**Unified Login guide**](/docs/unified-login/introduction). - -::: - - - - - - -```bash -npm i -s supertokens-react-native -# IMPORTANT: If you already have @react-native-async-storage/async-storage as a dependency, make sure the version is 1.12.1 or higher -npm i -s @react-native-async-storage/async-storage -``` - - - - - -Add to your `settings.gradle`: -```bash -dependencyResolutionManagement { - ... - repositories { - ... - maven { url 'https://jitpack.io' } - } -} -``` - -Add the following to you app level's `build.gradle`: -```bash -implementation 'com.github.supertokens:supertokens-android:X.Y.Z' -``` - -You can find the latest version of the SDK [here](https://github.com/supertokens/supertokens-android/releases) (ignore the `v` prefix in the releases). - - - - - -#### Using Cocoapods - -Add the Cocoapod dependency to your Podfile - -```bash -pod 'SuperTokensIOS' -``` - -#### Using Swift Package Manager - -Follow the [official documentation](https://developer.apple.com/documentation/xcode/adding-package-dependencies-to-your-app) to learn how to use Swift Package Manager to add dependencies to your project. - -When adding the dependency use the `master` branch after you enter the supertokens-ios repository URL: - -```bash -https://github.com/supertokens/supertokens-ios -``` - - - - - -Add the dependency to your pubspec.yaml - -```bash -supertokens_flutter: ^X.Y.Z -``` - -You can find the latest version of the SDK [here](https://github.com/supertokens/supertokens-flutter/releases) (ignore the `v` prefix in the releases). - - - - - - - - - -## 2) Call the `init` function - - - - - - - - - -Call the following `init` function at the start of your app (ideally on the global scope). - -```tsx -import SuperTokens from 'supertokens-web-js'; -import Session from 'supertokens-web-js/recipe/session'; -import ^{recipeNameCapitalLetters} from 'supertokens-web-js/recipe/^{codeImportRecipeName}' - -SuperTokens.init({ - appInfo: { - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}", - appName: "...", - }, - recipeList: [ - Session.init(), - ^{recipeNameCapitalLetters}.init(), - ], -}); -``` - - - - - - - - -Call the following `init` function at the start of your app (ideally on the global scope). - -```tsx -import supertokens from "supertokens-web-js-script"; -import supertokensSession from "supertokens-web-js-script/recipe/session"; -import supertokens^{recipeNameCapitalLetters} from 'supertokens-web-js-script/recipe/^{codeImportRecipeName}' -supertokens.init({ - appInfo: { - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}", - appName: "...", - }, - recipeList: [ - supertokensSession.init(), - supertokens^{recipeNameCapitalLetters}.init(), - ], -}); -``` - - - - - - - - - - - - - - - - -Call the following `init` function at the start of your app (ideally on the global scope). - -```tsx -import SuperTokens from 'supertokens-react-native'; - -SuperTokens.init({ - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}", -}); -``` - - - - - - - - - -Add the `SuperTokens.init` function call at the start of your application. - -```kotlin -import android.app.Application -import com.supertokens.session.SuperTokens - -class MainApplication: Application() { - override fun onCreate() { - super.onCreate() - - SuperTokens.Builder(this, "^{form_apiDomain}") - .apiBasePath("^{form_apiBasePath}") - .build() - } -} -``` - - - - - - - - - -Add the `SuperTokens.initialize` function call at the start of your application. - -```swift -import UIKit -import SuperTokensIOS - -fileprivate class ApplicationDelegate: UIResponder, UIApplicationDelegate { - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - do { - try SuperTokens.initialize( - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}" - ) - } catch SuperTokensError.initError(let message) { - // TODO: Handle initialization error - } catch { - // Some other error - } - - return true - } - -} -``` - - - - - - - - - -Add the `SuperTokens.init` function call at the start of your application. - -```dart -import 'package:supertokens_flutter/supertokens.dart'; - -void main() { - SuperTokens.init( - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}", - ); -} -``` - - - - - - - - - - - -## What to do next? -The above code snippet sets up session management network interceptors on the frontend. Our frontend SDK will now be able to automatically save and add session tokens to each request to your API layer and also do auto session refreshing. - -The next steps are to: -- Step 2: setup the backend SDK in your API layer -- Step 3: Setup the SuperTokens core (sign up for managed service, or self host it) -- Step 4: Enable the user management dashboard -- Use the frontend SDK's helper functions to build your own UI - follow along the docs in the "Using your own UI" section and you will find docs for this after "Step 4". diff --git a/v2/passwordless/custom-ui/init/user-management-dashboard/managing-user-roles-and-permissions.mdx b/v2/passwordless/custom-ui/init/user-management-dashboard/managing-user-roles-and-permissions.mdx index ec916c7a2..ae431bb4c 100644 --- a/v2/passwordless/custom-ui/init/user-management-dashboard/managing-user-roles-and-permissions.mdx +++ b/v2/passwordless/custom-ui/init/user-management-dashboard/managing-user-roles-and-permissions.mdx @@ -4,157 +4,7 @@ title: "Managing user roles and permissions" hide_title: true --- +import Redirector from '/src/components/Redirector'; - - - + -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import TabItem from '@theme/TabItem'; - -# Managing user roles and permissions - -You can manage [user roles and permissions](/docs/userroles/introduction) of your app from the user management dashboard. - -## Initialisation - -To begin configuring user roles and permissions on the user management dashboard, start by initializing the "UserRoles" recipe in the `recipeList` on the backend. - - - - -```tsx -import SuperTokens from "supertokens-node"; -import Dashboard from "supertokens-node/recipe/dashboard"; -import UserRoles from "supertokens-node/recipe/userroles" - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // TODO: Initialise other recipes if needed. - Dashboard.init(), - // highlight-start - UserRoles.init() - // highlight-end - ], -}); -``` -:::important Note - -Please note that the capability to manage roles and permissions from the user management dashboard is available only from Node SDK version `v16.6.0` onwards. - -::: - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/dashboard" - "github.com/supertokens/supertokens-golang/recipe/userroles" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - supertokens.Init(supertokens.TypeInput{ - RecipeList: []supertokens.Recipe{ - // TODO: Initialise other recipes if needed - dashboard.Init(nil), - // highlight-start - userroles.Init(nil), - // highlight-end - }, - }); -} -``` - - - - - -```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import dashboard, userroles - -init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore - recipe_list=[ - # TODO: Initialise other recipes if needed. - dashboard.init(), - # highlight-start - userroles.init() - # highlight-end - ] -) -``` - - - - - - - -## Managing roles and permissions - -When you first use the `UserRoles` recipe, the list of roles will be empty. To create roles, simply click on the "Add Role" button. - -No roles created - -This action will open a modal, enabling you to create a role along with its associated permissions. Permissions are essentially a list of strings assigned to a specific role. - -Create role - -After creating a role, the UI should display a list of all roles in your app. - -Roles list - -You can now preview the role you created by clicking on the role row. The modal provides options to edit or delete the role. - -Preview role - -## Manging roles and users - -To assign a specific role to a user, start by finding the user in the dashboard. Upon clicking the user, navigate to the user details page where you'll find a section for user roles. - -If the selected user is associated with multiple tenants, you can choose a 'tenantId' from the dropdown menu to specify the tenant for which you'd like to assign roles. - -Select tenant - -Click the edit button to start assigning roles. Then, select the "Assign Role" button, and a modal will appear with a list of available roles for assignment to this user. - -Assign role - -To remove a role assigned to a user, simply click on the "X" icon next to that specific role. - -View assigned role - - - - -:::important - -Our Python SDK doesn't currently support roles management via the user management UI. Instead, you can use the functions exposed by the SDK to manage roles and permissions. Check out the guide [here](/docs/userroles/introduction). - -::: - - - - - -:::important - -Our Golang SDK doesn't currently support roles management via the user management UI. Instead, you can use the functions exposed by the SDK to manage roles and permissions. Check out the guide [here](/docs/userroles/introduction). - -::: - - - - - - - diff --git a/v2/passwordless/custom-ui/init/user-management-dashboard/setup.mdx b/v2/passwordless/custom-ui/init/user-management-dashboard/setup.mdx index 0c108af83..ce23dfa60 100644 --- a/v2/passwordless/custom-ui/init/user-management-dashboard/setup.mdx +++ b/v2/passwordless/custom-ui/init/user-management-dashboard/setup.mdx @@ -4,329 +4,7 @@ title: "Setting up the dashboard" hide_title: true --- -# Setting up the Dashboard +import Redirector from '/src/components/Redirector'; - - - + -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import AppInfoForm from "/src/components/appInfoForm" -import CustomAdmonition from "/src/components/customAdmonition" - - -## Architecture - - - -Flowchart of architecture when using SuperTokens managed service - - -Flowchart of architecture when self-hosting SuperTokens - - - -The Backend SDK serves the user management dashboard which can be accessed on the `/auth/dashboard` path of your API domain. - - -## Initialise the dashboard recipe - - - -To get started, initialise the Dashboard recipe in the `recipeList`. - - - - -```tsx -import SuperTokens from "supertokens-node"; -import Dashboard from "supertokens-node/recipe/dashboard"; - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // TODO: Initialise other recipes - // highlight-start - Dashboard.init(), - // highlight-end - ], -}); -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/dashboard" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - supertokens.Init(supertokens.TypeInput{ - RecipeList: []supertokens.Recipe{ - // TODO: Initialise other recipes - // highlight-start - dashboard.Init(nil), - // highlight-end - }, - }); -} -``` - - - - -```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import dashboard - -init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore - recipe_list=[ - # TODO: Initialise other recipes - # highlight-start - dashboard.init(), - # highlight-end - ] -) -``` - - - - -## Viewing the dashboard - -:::important -The user management dashboard is served by the backend SDK, you have to use your API domain when trying to visit the dashboard. -::: - -Navigate to `^{form_apiDomain}^{form_apiBasePath}/dashboard` to view the dashboard. - -:::note -If you are using Next.js, upon integrating our backend SDK into your Next.js API folder, the dashboard will be accessible by default at `^{form_apiDomain}/api/auth/dashboard`. For frameworks other than Next.js, it can be accessed at `^{form_apiDomain}/auth/dashboard`. Should you have customized the `apiBasePath` configuration property, simply navigate to `^{form_apiDomain}^{form_apiBasePath}/dashboard` to access the dashboard. -::: - -Dashboard login screen UI - -## Creating dashboard credentials - - - -You can create 3 dashboard users* for free. - -If you need to create additional users: - -- For self hosted users, please [sign up](https://supertokens.com/auth) to generate a license key and follow the instructions sent to you by email. -- For managed service users, you can click on the "enable paid features" button on [our dashboard](https://supertokens.com/dashboard-saas), and follow the steps from there on. - -*: A dashboard user is a user that can log into and view the user management dashboard. These users are independent to the users of your application - - - -When you first setup SuperTokens, there are no credentials created for the dashboard. If you click the "Add a new user" button in the dashboard login screen you can see the command you need to execute in order to create credentials. - -Dashboard signup screen UI - -To create credentials you need to make a request to SuperTokens core. - -- The example above uses the demo core `https://try.supertokens.com`, replace this with the connection uri you pass to the backend SDK when initialising SuperTokens. -- Replace `` with your API key. If you are using a self hosted SuperTokens core there is no API key by default. In that case you can either skip or ignore the `api-key` header. -- Replace `` and `` with the appropriate values. - -:::caution -If using self hosted SuperTokens core, you need to make sure that you add an API key to the core in case it's exposed to the internet. Otherwise anyone will be able to create or modify dashboard users. - -You can add an API key to the core by following the instructions "Auth flow customizations" > "SuperTokens core settings" > "Adding API keys" page. -::: - -## Updating dashboard credentials - -You can update the email or password of existing credentials by using the "Forgot Password" button on the dashboard login page. - -Reset your password screen UI - -To update credentials you need to make a request to SuperTokens core. - -- The example above uses the demo core `https://try.supertokens.com`, replace this with the connection uri you pass to the backend SDK when initialising SuperTokens. -- Replace `` with your API key. If you are using a self hosted SuperTokens core there is no API key by default. In that case you can either skip or ignore the `api-key` header. -- Replace `` and `` with the appropriate values. You can use `newEmail` instead of `newPassword` if you want to update the email - - - - - -## Restricting access to dashboard users - -When using the dashboard recipe you can restrict access to certain features by providing a list of emails to be considered as "admins". When a dashboard user logs in with an email not present in this list, they will only be able to perform read operations and all write operations will result in the backend SDKs failing the request. - -You can provide an array of emails to the backend SDK when initialising the dashboard recipe: - -:::important -- Not providing an admins array will result in all dashboard users being allowed both read and write operations -- Providing an empty array as admins will result in all dashboard users having ONLY read access -::: - - - - -```tsx -import SuperTokens from "supertokens-node"; -import Dashboard from "supertokens-node/recipe/dashboard"; - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // TODO: Initialise other recipes - // highlight-start - Dashboard.init({ - admins: [ - "johndoe@gmail.com", - ], - }), - // highlight-end - ], -}); -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/dashboard" - "github.com/supertokens/supertokens-golang/supertokens" - "github.com/supertokens/supertokens-golang/recipe/dashboard/dashboardmodels" -) - -func main() { - supertokens.Init(supertokens.TypeInput{ - RecipeList: []supertokens.Recipe{ - // TODO: Initialise other recipes - // highlight-start - dashboard.Init(&dashboardmodels.TypeInput{ - Admins: &[]string{ - "johndoe@gmail.com", - }, - }), - // highlight-end - }, - }); -} -``` - - - - -```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import dashboard - -init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore - recipe_list=[ - # TODO: Initialise other recipes - # highlight-start - dashboard.init( - admins=[ - "johndoe@gmail.com", - ], - ), - # highlight-end - ] -) -``` - - - - -## Content Security Policy - - - - -If your backend returns a `Content-Security-Policy` header, you will encounter the following UI displaying the CSP violation details. Follow the instructions provided in this UI to make necessary adjustments to your backend CSP configuration. - -![CSP error handled UI](/img/dashboard/csp-error.png) - - -For example, to address the error message displayed in the above screenshot, you need to modify your `original policy`. In the given example, it appears as follows: - -```text -script-src: - 'self' - 'unsafe-inline' - https://google.com - -img-src: - https://google.com -``` - -To resolve this issue, make the following adjustments: - - -```text -script-src: - 'self' - 'unsafe-inline' - https://google.com - -img-src: - https://google.com - https://cdn.jsdelivr.net/gh/supertokens/ - -``` -Essentially, you need to include the domain listed as the `Blocked URI` in your violated directive block within your original policy. - - - -If you return a `Content-Security-Policy` header in from your backend, you will need to include the following directives for the user management dashboard to work correctly - -```text -script-src: - 'self' - 'unsafe-inline' - https://cdn.jsdelivr.net/gh/supertokens/ -img-src: - https://cdn.jsdelivr.net/gh/supertokens/ - https://purecatamphetamine.github.io/ -``` - - - - -If you return a `Content-Security-Policy` header in from your backend, you will need to include the following directives for the user management dashboard to work correctly - -```text -script-src: - 'self' - 'unsafe-inline' - https://cdn.jsdelivr.net/gh/supertokens/ -img-src: - https://cdn.jsdelivr.net/gh/supertokens/ - https://purecatamphetamine.github.io/ -``` - - - - - \ No newline at end of file diff --git a/v2/passwordless/custom-ui/init/user-management-dashboard/tenant-management/details.mdx b/v2/passwordless/custom-ui/init/user-management-dashboard/tenant-management/details.mdx index 4719a57cf..1397ce110 100644 --- a/v2/passwordless/custom-ui/init/user-management-dashboard/tenant-management/details.mdx +++ b/v2/passwordless/custom-ui/init/user-management-dashboard/tenant-management/details.mdx @@ -4,65 +4,7 @@ title: "Tenant Details" hide_title: true --- - - - +import Redirector from '/src/components/Redirector'; -# Tenant details + -Upon selection or creation of a tenant, you will be presented with the Tenant Details page. The various sections are explained below. - -Tenant details - -## Tenant ID and users - -The first section shows up the tenant ID and the number of users in that tenant. Clicking on `See Users` takes you to the [user management page](/docs/userdashboard/users-listing-and-details) where the users for the selected tenant can be viewed and managed. - -Tenant users - - -## Enabled Login Methods - -This section displays the various login methods available for the tenant. By enabling these toggles, you can make the corresponding login methods accessible to the users within the tenant. - -Appropriate recipes must be enabled to be able to turn on the login methods. For example, - -- to be able to turn on `emailpassword`, EmailPassword recipe must be initialised in the backend. -- to be able to turn on `OTP Phone`, Passwordless recipe must be initialised with flowType `USER_INPUT_CODE` and contactMethod `PHONE` - -:::info - -If you are using our Auth React SDK, make sure to enable [usesDynamicLoginMethods](/docs/passwordless/common-customizations/multi-tenancy/common-domain-login#step-3-tell-supertokens-about-the-saved-tenantid-from-the-previous-step) so that the frontend can automatically show the login methods based on the selection here. - -::: - -Login Methods - -## Secondary factors - -This section displays the various secondary factors available for the tenant. By enabling these toggles, the corresponding factor will be enabled for all users of the tenant. Refer [Multifactor Authentication docs](/docs/mfa/introduction) for more information. - -[MultiFactorAuth](/docs/mfa/backend-setup) recipe must be initialised to be able to enable Secondary Factors. - -Also, appropriate recipes must be initialised in the backend SDK to be able to use a secondary factor. For example, - -- to be able to turn on TOTP, TOTP recipe must be initialised in the backend. -- to be able to turn on `OTP Phone`, Passwordless recipe must be initialised with flowType `USER_INPUT_CODE` and contactMethod `PHONE` - -Secondary Factors - -## Core config - -Core Config - -This section shows the current config values in core for the tenant. You can edit some of these settings by clicking the `pencil` icon next to the property. - -Edit Core Config - -:::caution - -Some of the config values may not be editable since they are being inherited from the App. If using Supertokens managed hosting, they can be modified in the SaaS Dashboard. Else, if you are self-hosting the SuperTokens core, they will have to be edited via Docker env variables or the config.yaml file. - -::: - - diff --git a/v2/passwordless/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx b/v2/passwordless/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx index 32c5447bd..6ae231e40 100644 --- a/v2/passwordless/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx +++ b/v2/passwordless/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx @@ -4,29 +4,7 @@ title: "Overview" hide_title: true --- - - - +import Redirector from '/src/components/Redirector'; -# Tenant management overview + -You can now manage [tenants, login methods and third party providers](/docs/multitenancy/introduction) and [Multi factor authentication](/docs/mfa/introduction) of your app from the tenant management dashboard. - -Once the dashboard recipe is initialised, the tenant management should be available without requiring any additional steps. - -:::caution - -Currently, this is only available with our Node and Python SDKs. - -::: - -Tenant Management Landing - - -## Creating a new tenant - -Clicking on `Add Tenant` will prompt you to enter the tenant id. Once the tenant id is entered, click on `Create Now` to create the tenant. You will then be taken to the Tenant Details page where you can further manage the newly created tenant. - -Create Tenant - - diff --git a/v2/passwordless/custom-ui/init/user-management-dashboard/users-listing-and-details.mdx b/v2/passwordless/custom-ui/init/user-management-dashboard/users-listing-and-details.mdx index ff5945ed2..7be11467c 100644 --- a/v2/passwordless/custom-ui/init/user-management-dashboard/users-listing-and-details.mdx +++ b/v2/passwordless/custom-ui/init/user-management-dashboard/users-listing-and-details.mdx @@ -4,38 +4,7 @@ title: "Viewing user list and details" hide_title: true --- - - - +import Redirector from '/src/components/Redirector'; -# Viewing user list and details + -With the user management dashboard you can view the list of users and their details. You can also perform different operations on theses users as mentioned below. - -## Viewing users list - -If you have just created your app, you may not have any users to show on the dashboard. - -Empty dashboard screen UI - -Navigate to the your frontend app and create a user (via the sign up flow). On creation, if you head back to the dashboard and refresh the page, you will see that user: - -One user in dashboard screen UI - -## User details page - -When you select a user you can view detailed information about the user such as email, phone number, user metadata etc. - -User details page screen UI part one - -User details page screen UI part two - -You can edit user information and perform actions such as resetting a user's password or revoke sessions for a user. - -Change password modal UI - -:::important Note -Some features such as user metadata and email verification have to be enabled in your backend before you can use them in the user management dashboard -::: - - diff --git a/v2/passwordless/custom-ui/login-magic-link.mdx b/v2/passwordless/custom-ui/login-magic-link.mdx index 0088ec43a..31452d316 100644 --- a/v2/passwordless/custom-ui/login-magic-link.mdx +++ b/v2/passwordless/custom-ui/login-magic-link.mdx @@ -4,654 +4,8 @@ title: Magic link login hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" -import TabItem from '@theme/TabItem'; -import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" -import AppInfoForm from "/src/components/appInfoForm" -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import CustomAdmonition from "/src/components/customAdmonition" -# Magic link login + -There are three parts to Magic link based login: -- Creating and sending the magic link to the user. -- Allowing the user to resend a (new) magic link if they want. -- Consuming the link (when clicked) to login the user. - -:::note -The same flow applies during sign up and sign in. If the user is signing up, the `createdNewUser` boolean on the frontend and backend will be `true` (as the result of the consume code API call). -::: - -## Step 1: Sending the Magic link -SuperTokens allows you to send a magic link to a user's email or phone number. You have already configured this setting on the backend SDK `init` function call in "Initialisation" section. - -Start by making a form which asks the user for their email or phone, and then call the following API to create and send them a magic link - - - - - - - -```tsx -import { createCode } from "supertokens-web-js/recipe/passwordless"; - -async function sendMagicLink(email: string) { - try { - let response = await createCode({ - email - }); - /** - * For phone number, use this: - - let response = await createCode({ - phoneNumber: "+1234567890" - }); - - */ - - if (response.status === "SIGN_IN_UP_NOT_ALLOWED") { - // the reason string is a user friendly message - // about what went wrong. It can also contain a support code which users - // can tell you so you know why their sign in / up was not allowed. - window.alert(response.reason) - } else { - // Magic link sent successfully. - window.alert("Please check your email for the magic link"); - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you, - // or if the input email / phone number is not valid. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - -```tsx -import supertokensPasswordless from "supertokens-web-js-script/recipe/passwordless"; -async function sendMagicLink(email: string) { - try { - let response = await supertokensPasswordless.createCode({ - email - }); - /** - * For phone number, use this: - - let response = await supertokens^{recipeNameCapitalLetters}.createCode({ - phoneNumber: "+1234567890" - }); - - */ - - if (response.status === "SIGN_IN_UP_NOT_ALLOWED") { - // the reason string is a user friendly message - // about what went wrong. It can also contain a support code which users - // can tell you so you know why their sign in / up was not allowed. - window.alert(response.reason) - } else { - // Magic link sent successfully. - window.alert("Please check your email for the magic link"); - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you, - // or if the input email / phone number is not valid. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - - - - - - - -For email based login - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup/code' \ ---header 'Content-Type: application/json; charset=utf-8' \ ---data-raw '{ - "email": "johndoe@gmail.com" -}' -``` - -For phone number based login -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup/code' \ ---header 'Content-Type: application/json; charset=utf-8' \ ---data-raw '{ - "phoneNumber": "+1234567890" -}' -``` - - - -The response body from the API call has a `status` property in it: -- `status: "OK"`: This means that the magic link was successfully sent. -- `status: "GENERAL_ERROR"`: This is possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend, or if the input email or password failed the backend validation logic. -- `status: "SIGN_IN_UP_NOT_ALLOWED"`: This can happen during automatic account linking or during MFA. The `reason` prop that's in the response body contains a support code using which you can see why the sign in / up was not allowed. - -The response from the API call is the following object (in case of `status: "OK"`): -```json -{ - status: "OK"; - deviceId: string; - preAuthSessionId: string; - flowType: "USER_INPUT_CODE" | "MAGIC_LINK" | "USER_INPUT_CODE_AND_MAGIC_LINK"; - fetchResponse: Response; // raw fetch response from the API call -} -``` - -You want to save the `deviceId` and `preAuthSessionId` on the frontend storage. These will be useful to: -- Resend a new magic link. -- Detect if the user has already sent a magic link before or if this is an entirely new login attempt. This distinction can be important if you have different UI for these two states. For example, if this info already exists, you do not want to show the user an input box to enter their email / phone, and instead want to show them the resend link button. - - - - - -### Changing the magic link URL, or deep linking it to your app -By default, the magic link will point to the `websiteDomain` that is configured on the backend, on the `/auth/verify` route (where `/auth` is the default value of `websiteBasePath`). - -If you want to change this to a different path, a different domain, or deep link it to your mobile / desktop app, then you can do so on the backend in the following way: - - - - -```tsx -import SuperTokens from "supertokens-node"; -import Passwordless from "supertokens-node/recipe/passwordless"; -import Session from "supertokens-node/recipe/session"; - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "..." - }, - recipeList: [ - Passwordless.init({ - contactMethod: "EMAIL", // This example will work with any contactMethod - // This example works with the "USER_INPUT_CODE_AND_MAGIC_LINK" and "MAGIC_LINK" flows. - flowType: "USER_INPUT_CODE_AND_MAGIC_LINK", - - // highlight-start - emailDelivery: { - // highlight-start - override: (originalImplementation) => { - return { - ...originalImplementation, - sendEmail: async function (input) { - return originalImplementation.sendEmail({ - ...input, - urlWithLinkCode: input.urlWithLinkCode?.replace( - // This is: `${websiteDomain}${websiteBasePath}/verify` - "http://localhost:3000/auth/verify", - "http://your.domain.com/your/path" - ) - }) - } - } - } - } - // highlight-end - }), - Session.init({ /* ... */ }) - ] -}); -``` - - - -```go -import ( - "strings" - - "github.com/supertokens/supertokens-golang/ingredients/emaildelivery" - "github.com/supertokens/supertokens-golang/recipe/passwordless" - "github.com/supertokens/supertokens-golang/recipe/passwordless/plessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - supertokens.Init(supertokens.TypeInput{ - RecipeList: []supertokens.Recipe{ - passwordless.Init(plessmodels.TypeInput{ - EmailDelivery: &emaildelivery.TypeInput{ - // highlight-start - Override: func(originalImplementation emaildelivery.EmailDeliveryInterface) emaildelivery.EmailDeliveryInterface { - ogSendEmail := *originalImplementation.SendEmail - (*originalImplementation.SendEmail) = func(input emaildelivery.EmailType, userContext supertokens.UserContext) error { - // By default: `${websiteDomain}/${websiteBasePath}/verify` - newUrl := strings.Replace( - *input.PasswordlessLogin.UrlWithLinkCode, - "http://localhost:3000/auth/verify", - "http://localhost:3000/custom/path", - 1, - ) - input.PasswordlessLogin.UrlWithLinkCode = &newUrl - return ogSendEmail(input, userContext) - } - return originalImplementation - }, - // highlight-end - }, - }), - }, - }) -} -``` - - - - -```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe.passwordless.types import EmailDeliveryOverrideInput, EmailTemplateVars -from supertokens_python.recipe import passwordless -from typing import Dict, Any -from supertokens_python.ingredients.emaildelivery.types import EmailDeliveryConfig - -def custom_email_deliver(original_implementation: EmailDeliveryOverrideInput) -> EmailDeliveryOverrideInput: - original_send_email = original_implementation.send_email - - # highlight-start - async def send_email(template_vars: EmailTemplateVars, user_context: Dict[str, Any]) -> None: - assert template_vars.url_with_link_code is not None - # By default: `${websiteDomain}/${websiteBasePath}/verify` - template_vars.url_with_link_code = template_vars.url_with_link_code.replace( - "http://localhost:3000/auth/verify", "http://localhost:3000/custom/path") - return await original_send_email(template_vars, user_context) - # highlight-end - - original_implementation.send_email = send_email - return original_implementation - -init( - app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore - recipe_list=[ - passwordless.init( - contact_config="", # type: ignore # typecheck-only, removed from output - flow_type="USER_INPUT_CODE", # typecheck-only, removed from output - email_delivery=EmailDeliveryConfig(override=custom_email_deliver) - ) - ] -) -``` - - - - - - -For a multi tenant setup, the input to the sendEmail function will also contain the tenantId. You can use this to determine the correct value to set for the websiteDomain in the generated link. - - - -## Step 2: Resending a (new) Magic link -After sending the initial magic link to the user, you may want to display a resend button to them. When the user clicks on this button, you should call the following API - - - - - - - -```tsx -import { resendCode, clearLoginAttemptInfo } from "supertokens-web-js/recipe/passwordless"; - -async function resendMagicLink() { - try { - let response = await resendCode(); - - if (response.status === "RESTART_FLOW_ERROR") { - // this can happen if the user has already successfully logged in into - // another device whilst also trying to login to this one. - - // we clear the login attempt info that was added when the createCode function - // was called - so that if the user does a page reload, they will now see the - // enter email / phone UI again. - await clearLoginAttemptInfo(); - window.alert("Login failed. Please try again"); - window.location.assign("/auth") - } else { - // Magic link resent successfully. - window.alert("Please check your email for the magic link"); - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - -```tsx -import supertokensPasswordless from "supertokens-web-js-script/recipe/passwordless"; -async function resendMagicLink() { - try { - let response = await supertokensPasswordless.resendCode(); - - if (response.status === "RESTART_FLOW_ERROR") { - // this can happen if the user has already successfully logged in into - // another device whilst also trying to login to this one. - - // we clear the login attempt info that was added when the createCode function - // was called - so that if the user does a page reload, they will now see the - // enter email / phone UI again. - await supertokensPasswordless.clearLoginAttemptInfo(); - window.alert("Login failed. Please try again"); - window.location.assign("/auth") - } else { - // Magic link resent successfully. - window.alert("Please check your email for the magic link"); - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - - - - - - - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup/code/resend' \ ---header 'Content-Type: application/json; charset=utf-8' \ ---data-raw '{ - "deviceId": "...", - "preAuthSessionId": "...." -}' -``` - - - -The response body from the API call has a `status` property in it: -- `status: "OK"`: This means that the magic link was successfully sent. -- `status: "RESTART_FLOW_ERROR"`: This can happen if the user has already successfully logged in into another device whilst also trying to login to this one. You want to take the user back to the login screen where they can enter their email / phone number again. Be sure to remove the stored `deviceId` and `preAuthSessionId` from the frontend storage. -- `status: "GENERAL_ERROR"`: This is possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. - - - - - -### How to detect if the user is in (Step 1) or in (Step 2) state? -If you are building the UI for (Step 1) and (Step 2) on the same page, and if the user refreshes the page, you need a way to know which UI to show - the enter email / phone number form; or the resend magic link form. - - - - - - - -```tsx -import { getLoginAttemptInfo } from "supertokens-web-js/recipe/passwordless"; - -async function hasInitialMagicLinkBeenSent() { - return await getLoginAttemptInfo() !== undefined; -} -``` - - - - -```tsx -import supertokensPasswordless from "supertokens-web-js-script/recipe/passwordless"; -async function hasInitialMagicLinkBeenSent() { - return await supertokensPasswordless.getLoginAttemptInfo() !== undefined; -} -``` - - - - -If `hasInitialMagicLinkBeenSent` returns `true`, it means that the user has already sent the initial magic link to themselves, and you can show the resend link UI (Step 2). Else show a form asking them to enter their email / phone number (Step 1). - - - - - -Since you save the `preAuthSessionId` and `deviceId` after the initial magic link is sent, you can know if the user is in (Step 1) vs (Step 2) by simply checking if these tokens are stored on the device. - -If they aren't, you should follow (Step 1), else follow (Step 2). - -:::important -You need to clear these tokens if the user navigates away from the (Step 2) page, or if you get a `RESTART_FLOW_ERROR` at any point in time from an API call, or if the user has successfully logged in. -::: - - - - - -## Step 3: Consuming the Magic link -This section talks about what needs to be done when the user clicks on the Magic link. There are two situations here: -- The user clicks the Magic link on the same browser & device as the one they had started the flow on. -- The user clicks the link on a different browser or device. - -In order to detect which it is, you can do the following: - - - - - - - -```tsx -import { getLoginAttemptInfo } from "supertokens-web-js/recipe/passwordless"; - -async function isThisSameBrowserAndDevice() { - return await getLoginAttemptInfo() !== undefined; -} -``` - - - - -```tsx -import supertokensPasswordless from "supertokens-web-js-script/recipe/passwordless"; -async function isThisSameBrowserAndDevice() { - return await supertokensPasswordless.getLoginAttemptInfo() !== undefined; -} -``` - - - - - - - - -Since you save the `preAuthSessionId` and `deviceId`, you can check if they exist on the app. If they do, then it's the same device that the user has opened the link on, else it's a different device. - - - - - -### If on the same device & browser - - - - -On page load, you can consume the magic link by calling the following function - - - - -```tsx -import { consumeCode, clearLoginAttemptInfo } from "supertokens-web-js/recipe/passwordless"; - -async function handleMagicLinkClicked() { - try { - let response = await consumeCode(); - - if (response.status === "OK") { - // we clear the login attempt info that was added when the createCode function - // was called since the login was successful. - await clearLoginAttemptInfo(); - if (response.createdNewRecipeUser && response.user.loginMethods.length === 1) { - // user sign up success - } else { - // user sign in success - } - window.location.assign("/home") - } else { - // this can happen if the magic link has expired or is invalid - // or if it was denied due to security reasons in case of automatic account linking - - // we clear the login attempt info that was added when the createCode function - // was called - so that if the user does a page reload, they will now see the - // enter email / phone UI again. - await clearLoginAttemptInfo(); - window.alert("Login failed. Please try again"); - window.location.assign("/auth") - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - -```tsx -import supertokensPasswordless from "supertokens-web-js-script/recipe/passwordless"; -async function handleMagicLinkClicked() { - try { - let response = await supertokensPasswordless.consumeCode(); - - if (response.status === "OK") { - // we clear the login attempt info that was added when the createCode function - // was called since the login was successful. - await supertokensPasswordless.clearLoginAttemptInfo(); - if (response.createdNewRecipeUser && response.user.loginMethods.length === 1) { - // user sign up success - } else { - // user sign in success - } - window.location.assign("/home") - } else { - // this can happen if the magic link has expired or is invalid - // or if it was denied due to security reasons in case of automatic account linking - - // we clear the login attempt info that was added when the createCode function - // was called - so that if the user does a page reload, they will now see the - // enter email / phone UI again. - await supertokensPasswordless.clearLoginAttemptInfo(); - window.alert("Login failed. Please try again"); - window.location.assign("/auth") - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - - - - - -You need to extract the `linkCode` and `preAuthSessionId` from the Magic link. For example, if the Magic link is - -```text -https://example.com/auth/verify?preAuthSessionId=PyIwyA6VjdjNF5ggMV960rs3QXupRP2PEg2KcN5oi8s=#s4hxpBPnRC3xwBsCkFU228lh_CWe5HUBMRPowajsrgs= -``` - -Then the `preAuthSessionId` is the value of the query param `preAuthSessionId` (`PyIwyA6VjdjNF5ggMV960rs3QXupRP2PEg2KcN5oi8s=` in the example), and the `linkCode` is the part after the `#` (`s4hxpBPnRC3xwBsCkFU228lh_CWe5HUBMRPowajsrgs=` in our example). - -We can then use these to call the consume API - - - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup/code/consume' \ ---header 'Content-Type: application/json; charset=utf-8' \ ---data-raw '{ - "linkCode": "s4hxpBPnRC3xwBsCkFU228lh_CWe5HUBMRPowajsrgs=", - "preAuthSessionId": "PyIwyA6VjdjNF5ggMV960rs3QXupRP2PEg2KcN5oi8s=" -}' -``` - - - - - -For a multi tenancy setup, the `` value can be fetched from `tenantId` query parameter from the magic link. If it's not there in the link, you can use the value `"public"` (which is the default tenant). - - - -The response body from the API call has a `status` property in it: -- `status: "OK"`: User sign in / up was successful. The response also contains more information about the user, for example their user ID, and if it was a new user or existing user. -- `status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" | "RESTART_FLOW_ERROR"`: These responses indicate that the Magic link was invalid or expired. -- `status: "GENERAL_ERROR"`: This is possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. -- `status: "SIGN_IN_UP_NOT_ALLOWED"`: This can happen during automatic account linking or during MFA. The `reason` prop that's in the response body contains a support code using which you can see why the sign in / up was not allowed. - - - - - -:::note -On success, the backend will send back session tokens as part of the response headers which will be automatically handled by our frontend SDK for you. -::: - -### If on a different device or browser -In this case, you want to show some UI that requires a user interaction before consuming the magic link. This is to protect against email clients opening the magic link on their servers and consuming the link. For example, you could show a button with text like - "Click here to login into this device". - -On click, you can consume the magic link to log the user into that device. Follow the instructions in the above section to know which function / API to call. - -## See also -- [Post sign in / sign up action](../common-customizations/handling-signinup-success) -- [Changing the magic link lifetime](../common-customizations/change-code-lifetime) -- Changing [the content of](../email-delivery/about) or how [emails and / or SMS are sent](../email-delivery/about) -- [Generating magic links without user action](../common-customizations/generating-magic-link-manually) -- [Changing email or phone number validation logic](../common-customizations/sign-in-and-up/change-email-phone-validation) -- [Customising user ID format](../common-customizations/userid-format) diff --git a/v2/passwordless/custom-ui/login-otp.mdx b/v2/passwordless/custom-ui/login-otp.mdx index 6427032a7..2e86f70c1 100644 --- a/v2/passwordless/custom-ui/login-otp.mdx +++ b/v2/passwordless/custom-ui/login-otp.mdx @@ -4,471 +4,9 @@ title: OTP login hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" -import TabItem from '@theme/TabItem'; -import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" -import AppInfoForm from "/src/components/appInfoForm" -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -# OTP login + -There are three parts to OTP login: -- Creating and sending the OTP to the user. -- Allowing the user to resend a (new) OTP if they want. -- Validating the user's input OTP to login the user. -:::note -The same flow applies during sign up and sign in. If the user is signing up, the `createdNewUser` boolean on the frontend and backend will be `true` (as the result of the consume code API call). -::: - -## Step 1: Creating and sending the OTP -SuperTokens allows you to send an OTP to a user's email or phone number. You have already configured this setting on the backend SDK `init` function call in "Initialisation" section. - -Start by making a form which asks the user for their email or phone, and then call the following API to create and send them an OTP. - - - - - - - -```tsx -import { createCode } from "supertokens-web-js/recipe/passwordless"; - -async function sendOTP(email: string) { - try { - let response = await createCode({ - email - }); - /** - * For phone number, use this: - - let response = await createPasswordlessCode({ - phoneNumber: "+1234567890" - }); - - */ - - if (response.status === "SIGN_IN_UP_NOT_ALLOWED") { - // the reason string is a user friendly message - // about what went wrong. It can also contain a support code which users - // can tell you so you know why their sign in / up was not allowed. - window.alert(response.reason) - } else { - // OTP sent successfully. - window.alert("Please check your email for an OTP"); - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you, - // or if the input email / phone number is not valid. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - -```tsx -import supertokensPasswordless from "supertokens-web-js-script/recipe/passwordless"; -async function sendOTP(email: string) { - try { - let response = await supertokensPasswordless.createCode({ - email - }); - /** - * For phone number, use this: - - let response = await supertokens^{recipeNameCapitalLetters}.createCode({ - phoneNumber: "+1234567890" - }); - - */ - - if (response.status === "SIGN_IN_UP_NOT_ALLOWED") { - // the reason string is a user friendly message - // about what went wrong. It can also contain a support code which users - // can tell you so you know why their sign in / up was not allowed. - window.alert(response.reason) - } else { - // OTP sent successfully. - window.alert("Please check your email for an OTP"); - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you, - // or if the input email / phone number is not valid. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - - - - - - - -For email based login - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup/code' \ ---header 'Content-Type: application/json; charset=utf-8' \ ---data-raw '{ - "email": "johndoe@gmail.com" -}' -``` - -For phone number based login -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup/code' \ ---header 'Content-Type: application/json; charset=utf-8' \ ---data-raw '{ - "phoneNumber": "+1234567890" -}' -``` - - - -The response body from the API call has a `status` property in it: -- `status: "OK"`: This means that the OTP was successfully sent. -- `status: "GENERAL_ERROR"`: This is possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend, or if the input email or password failed the backend validation logic. -- `status: "SIGN_IN_UP_NOT_ALLOWED"`: This can happen during automatic account linking or during MFA. The `reason` prop that's in the response body contains a support code using which you can see why the sign in / up was not allowed. - -The response from the API call is the following object (in case of `status: "OK"`): -```json -{ - status: "OK"; - deviceId: string; - preAuthSessionId: string; - flowType: "USER_INPUT_CODE" | "MAGIC_LINK" | "USER_INPUT_CODE_AND_MAGIC_LINK"; - fetchResponse: Response; // raw fetch response from the API call -} -``` - -You want to save the `deviceId` and `preAuthSessionId` on the frontend storage. These will be useful to: -- Resend a new OTP. -- Detect if the user has already sent an OTP before or if this is an entirely new login attempt. This distinction can be important if you have different UI for these two states. For example, if this info already exists, you do not want to show the user an input box to enter their email / phone, and instead want to show them the enter OTP form with a resend button. -- Verify the user's input OTP. - - - - - -## Step 2: Resending a (new) OTP -After sending the initial OTP to the user, you may want to display a resend button to them. When the user clicks on this button, you should call the following API - - - - - - - -```tsx -import { resendCode, clearLoginAttemptInfo } from "supertokens-web-js/recipe/passwordless"; - -async function resendOTP() { - try { - let response = await resendCode(); - - if (response.status === "RESTART_FLOW_ERROR") { - // this can happen if the user has already successfully logged in into - // another device whilst also trying to login to this one. - - // we clear the login attempt info that was added when the createCode function - // was called - so that if the user does a page reload, they will now see the - // enter email / phone UI again. - await clearLoginAttemptInfo(); - window.alert("Login failed. Please try again"); - window.location.assign("/auth") - } else { - // OTP resent successfully. - window.alert("Please check your email for the OTP"); - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - -```tsx -import supertokensPasswordless from "supertokens-web-js-script/recipe/passwordless"; -async function resendOTP() { - try { - let response = await supertokensPasswordless.resendCode(); - - if (response.status === "RESTART_FLOW_ERROR") { - // this can happen if the user has already successfully logged in into - // another device whilst also trying to login to this one. - - // we clear the login attempt info that was added when the createCode function - // was called - so that if the user does a page reload, they will now see the - // enter email / phone UI again. - await supertokensPasswordless.clearLoginAttemptInfo(); - window.alert("Login failed. Please try again"); - window.location.assign("/auth") - } else { - // OTP resent successfully. - window.alert("Please check your email for the OTP"); - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - - - - - - - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup/code/resend' \ ---header 'Content-Type: application/json; charset=utf-8' \ ---data-raw '{ - "deviceId": "...", - "preAuthSessionId": "...." -}' -``` - - - -The response body from the API call has a `status` property in it: -- `status: "OK"`: This means that the OTP was successfully sent. -- `status: "RESTART_FLOW_ERROR"`: This can happen if the user has already successfully logged in into another device whilst also trying to login to this one. You want to take the user back to the login screen where they can enter their email / phone number again. Be sure to remove the stored `deviceId` and `preAuthSessionId` from the frontend storage. -- `status: "GENERAL_ERROR"`: This is possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. - - - - - -### How to detect if the user is in (Step 1) or in (Step 2) state? -If you are building the UI for (Step 1) and (Step 2) on the same page, and if the user refreshes the page, you need a way to know which UI to show - the enter email / phone number form; or enter OTP + resend OTP form. - - - - - - - -```tsx -import { getLoginAttemptInfo } from "supertokens-web-js/recipe/passwordless"; - -async function hasInitialOTPBeenSent() { - return await getLoginAttemptInfo() !== undefined; -} -``` - - - - -```tsx -import supertokensPasswordless from "supertokens-web-js-script/recipe/passwordless"; -async function hasInitialOTPBeenSent() { - return await supertokensPasswordless.getLoginAttemptInfo() !== undefined; -} -``` - - - - -If `hasInitialOTPBeenSent` returns `true`, it means that the user has already sent the initial OTP to themselves, and you can show the enter OTP form + resend OTP button (Step 2). Else show a form asking them to enter their email / phone number (Step 1). - - - - - -Since you save the `preAuthSessionId` and `deviceId` after the initial OTP is sent, you can know if the user is in (Step 1) vs (Step 2) by simply checking if these tokens are stored on the device. - -If they aren't, you should follow (Step 1), else follow (Step 2). - -:::important -You need to clear these tokens if the user navigates away from the (Step 2) page, or if you get a `RESTART_FLOW_ERROR` at any point in time from an API call, or if the user has successfully logged in. -::: - - - - - -## Step 3: Verifying the input OTP -When the user enters an OTP, you want to call the following API to verify it - - - - - - - -```tsx -import { consumeCode, clearLoginAttemptInfo } from "supertokens-web-js/recipe/passwordless"; - -async function handleOTPInput(otp: string) { - try { - let response = await consumeCode({ - userInputCode: otp - }); - - if (response.status === "OK") { - // we clear the login attempt info that was added when the createCode function - // was called since the login was successful. - await clearLoginAttemptInfo(); - if (response.createdNewRecipeUser && response.user.loginMethods.length === 1) { - // user sign up success - } else { - // user sign in success - } - window.location.assign("/home") - } else if (response.status === "INCORRECT_USER_INPUT_CODE_ERROR") { - // the user entered an invalid OTP - window.alert("Wrong OTP! Please try again. Number of attempts left: " + (response.maximumCodeInputAttempts - response.failedCodeInputAttemptCount)); - } else if (response.status === "EXPIRED_USER_INPUT_CODE_ERROR") { - // it can come here if the entered OTP was correct, but has expired because - // it was generated too long ago. - window.alert("Old OTP entered. Please regenerate a new one and try again"); - } else { - // this can happen if the user tried an incorrect OTP too many times. - // or if it was denied due to security reasons in case of automatic account linking - - // we clear the login attempt info that was added when the createCode function - // was called - so that if the user does a page reload, they will now see the - // enter email / phone UI again. - await clearLoginAttemptInfo(); - window.alert("Login failed. Please try again"); - window.location.assign("/auth") - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - -```tsx -import supertokensPasswordless from "supertokens-web-js-script/recipe/passwordless"; -async function handleOTPInput(otp: string) { - try { - let response = await supertokensPasswordless.consumeCode({ - userInputCode: otp - }); - - if (response.status === "OK") { - // we clear the login attempt info that was added when the createCode function - // was called since the login was successful. - await supertokensPasswordless.clearLoginAttemptInfo(); - if (response.createdNewRecipeUser && response.user.loginMethods.length === 1) { - // user sign up success - } else { - // user sign in success - } - window.location.assign("/home") - } else if (response.status === "INCORRECT_USER_INPUT_CODE_ERROR") { - // the user entered an invalid OTP - window.alert("Wrong OTP! Please try again. Number of attempts left: " + (response.maximumCodeInputAttempts - response.failedCodeInputAttemptCount)); - } else if (response.status === "EXPIRED_USER_INPUT_CODE_ERROR") { - // it can come here if the entered OTP was correct, but has expired because - // it was generated too long ago. - window.alert("Old OTP entered. Please regenerate a new one and try again"); - } else { - // this can happen if the user tried an incorrect OTP too many times. - // or if it was denied due to security reasons in case of automatic account linking - - // we clear the login attempt info that was added when the createCode function - // was called - so that if the user does a page reload, they will now see the - // enter email / phone UI again. - await supertokensPasswordless.clearLoginAttemptInfo(); - window.alert("Login failed. Please try again"); - window.location.assign("/auth") - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - - - - - - - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup/code/consume' \ ---header 'Content-Type: application/json; charset=utf-8' \ ---data-raw '{ - "deviceId": "...", - "preAuthSessionId": "...", - "userInputCode": "" -}' -``` - - - -The response body from the API call has a `status` property in it: -- `status: "OK"`: User sign in / up was successful. The response also contains more information about the user, for example their user ID, and if it was a new user or existing user. -- `status: "INCORRECT_USER_INPUT_CODE_ERROR"`: The entered OTP is invalid. The response also contains information about the maximum number of retries and the number of failed attempts so far. -- `status: "EXPIRED_USER_INPUT_CODE_ERROR"`: The entered OTP is too old. You should ask the user to resend a new OTP and try again. -- `status: "RESTART_FLOW_ERROR"`: These responses that the user tried invalid OTPs too many times. -- `status: "GENERAL_ERROR"`: This is possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. -- `status: "SIGN_IN_UP_NOT_ALLOWED"`: This can happen during automatic account linking or during MFA. The `reason` prop that's in the response body contains a support code using which you can see why the sign in / up was not allowed. - - - - - -:::note -On success, the backend will send back session tokens as part of the response headers which will be automatically handled by our frontend SDK for you. -::: - -## See also -- [Post sign in / sign up action](../common-customizations/handling-signinup-success) -- [Changing the OTP's lifetime](../common-customizations/change-code-lifetime) -- [Changing OTP format](../common-customizations/sign-in-and-up/change-otp-format) -- [Changing the number of max retries for an OTP](../common-customizations/sign-in-and-up/change-maximum-retries) -- Changing [the content of](../email-delivery/about) or how [emails and / or SMS are sent](../email-delivery/about) -- [Changing email or phone number validation logic](../common-customizations/sign-in-and-up/change-email-phone-validation) -- [Customising user ID format](../common-customizations/userid-format) diff --git a/v2/passwordless/custom-ui/multitenant-login.mdx b/v2/passwordless/custom-ui/multitenant-login.mdx index c201a008f..bf2091f6c 100644 --- a/v2/passwordless/custom-ui/multitenant-login.mdx +++ b/v2/passwordless/custom-ui/multitenant-login.mdx @@ -1,256 +1,11 @@ --- id: multitenant-login -title: "Multitenant login" +title: "Multitenant and Enterprise SSO login" hide_title: true --- -import AppInfoForm from "/src/components/appInfoForm" -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import PythonSyncAsyncSubTabs from "/src/components/tabs/PythonSyncAsyncSubTabs"; -import CoreVersionSubTabs from "/src/components/tabs/CoreVersionSubTabs"; -import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs"; -import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" -import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" -import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" +import Redirector from '/src/components/Redirector'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import MultiTenancyPaidBanner from '../../community/reusableMD/multitenancy/MultiTenancyPaidBanner.mdx' -import CoreInjector from "/src/components/coreInjector" - - - + - - -# Multitenant login - -Multitenant login is a feature that lets you customize the login experience for each of your customers. For example, a customer `customer1` hosted on `customer1.yourdomain.com` can have passwordless login (using this recipe), and another customer `customer2` hosted on `customer2.yourdomain.com` can have login with `Active Directory` and `Facebook` (using the thirdparty recipe). - - - - - - - -## Step 1: Create and configure a new tenant in SuperTokens core - -You can create a new tenant using our backend SDKs or via a `cURL` command to the core. - - - - - - - - - - - -```tsx -import Multitenancy from "supertokens-node/recipe/multitenancy"; -import { FactorIds } from "supertokens-node/recipe/multifactorauth" - -async function createNewTenant() { - - // highlight-start - let resp = await Multitenancy.createOrUpdateTenant("customer1", { - firstFactors: [FactorIds.OTP_PHONE, FactorIds.OTP_EMAIL, FactorIds.LINK_PHONE, FactorIds.LINK_EMAIL] - }); - // highlight-end - - if (resp.createdNew) { - // new tenant was created - } else { - // existing tenant's config was modified. - } -} -``` - -In the example above, we have added all factor IDs related to the passwordless recipe. However, you can choose to use a subset of them. The factor IDs are described below: - -- Passwordless: - - With email OTP: `FactorIds.OTP_EMAIL` - - With SMS OTP: `FactorIds.OTP_PHONE` - - With email magic link: `FactorIds.LINK_EMAIL` - - With SMS magic link: `FactorIds.LINK_PHONE` - - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/multitenancy" - "github.com/supertokens/supertokens-golang/recipe/multitenancy/multitenancymodels" -) - -func main() { - tenantId := "customer1" - passwordlessEnabled := true - - // highlight-start - resp, err := multitenancy.CreateOrUpdateTenant(tenantId, multitenancymodels.TenantConfig{ - PasswordlessEnabled: &passwordlessEnabled, - }) - // highlight-end - - if err != nil { - // handle error - } - if resp.OK.CreatedNew { - // new tenant was created - } else { - // existing tenant's config was modified. - } -} -``` - - - - - - - -```python -from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate -from supertokens_python.recipe.multifactorauth.types import FactorIds - - -async def some_func(): - response = await create_or_update_tenant( - "customer1", - TenantConfigCreateOrUpdate( - first_factors=[ - FactorIds.OTP_PHONE, - FactorIds.OTP_EMAIL, - FactorIds.LINK_PHONE, - FactorIds.LINK_EMAIL, - ] - ), - ) - - if response.status != "OK": - print("Handle error") - elif response.created_new: - print("new tenant was created") - else: - print("existing tenant's config was modified.") -``` - - - - - -```python -from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate -from supertokens_python.recipe.multifactorauth.types import FactorIds - - -async def some_func(): - response = create_or_update_tenant( - "customer1", - TenantConfigCreateOrUpdate( - first_factors=[ - FactorIds.OTP_PHONE, - FactorIds.OTP_EMAIL, - FactorIds.LINK_PHONE, - FactorIds.LINK_EMAIL, - ] - ), - ) - - if response.status != "OK": - print("Handle error") - elif response.created_new: - print("new tenant was created") - else: - print("existing tenant's config was modified.") -``` - - - - - - - - - - - - - - -```bash -curl --location --request PUT '^{coreInjector_uri_without_quotes}/recipe/multitenancy/tenant/v2' \ ---header 'api-key: ^{coreInjector_api_key_without_quotes}' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "tenantId": "customer1", - "firstFactors": ["otp-email", "otp-phone", "link-email", "link-phone"] -}' -``` - -In the example above, we have added all factor IDs related to the passwordless recipe. However, you can choose to use a subset of them. The factor IDs are described below: - -- Passwordless: - - With email OTP: `otp-email` - - With SMS OTP: `otp-phone` - - With email magic link: `link-email` - - With SMS magic link: `link-phone` - - - - - - -```bash -curl --location --request PUT '^{coreInjector_uri_without_quotes}/recipe/multitenancy/tenant' \ ---header 'api-key: ^{coreInjector_api_key_without_quotes}' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "tenantId": "customer1", - "passwordlessEnabled": true -}' -``` - - - - - - - - - - - -Create Tenant - - - - - - - - - - - - -## Step 2: Build your multi tenant a UX flow - -The most common multi tenant flows are: -- [Tenants use a common domain to login](../common-customizations/multi-tenancy/common-domain-login): All tenants login using the same page (for example, `example.com/auth`) and are optionally redirected to their sub domain post login. At the start of the login flow, the customer will have to input their tenantId / workspace URL / identifier - as defined by you, and the login methods shown would be based on their tenantId. -- [Tenants use their sub domain to login](../common-customizations/multi-tenancy/sub-domain-login): Here, each tenant has a sub domain assigned to them (for example `customer1.example.com`, `customer2.example.com`, ...), and they would visit their sub domain to login and access their app. Each sub domain's login experience may be different (as defined by you or the tenant). - -SuperTokens is flexible enough to allow other forms of UX as well, but since the above two flow are most common, we provide dedicated docs for them (see the links above). - -## See also - -- Multi tenant [single domain](../common-customizations/multi-tenancy/common-domain-login) or [sub domain](../common-customizations/multi-tenancy/sub-domain-login) login. -- [Tenant data isolation](../common-customizations/multi-tenancy/new-tenant-config#providing-additional-configuration-per-tenant). - - diff --git a/v2/passwordless/custom-ui/securing-routes.mdx b/v2/passwordless/custom-ui/securing-routes.mdx index 665ebb50b..adf124a95 100644 --- a/v2/passwordless/custom-ui/securing-routes.mdx +++ b/v2/passwordless/custom-ui/securing-routes.mdx @@ -4,590 +4,8 @@ title: "Securing your API and frontend routes" hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -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" -import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" -import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" -import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" + -# Securing your API and frontend routes - -## Protecting APIs - - - - - - - -### Requiring an active session - -For your APIs that require a user to be logged in, use the `verifySession` middleware - - - - - - -```tsx -import express from "express"; -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import { SessionRequest } from "supertokens-node/framework/express"; - -let app = express(); - -// highlight-start -app.post("/like-comment", verifySession(), (req: SessionRequest, res) => { - let userId = req.session!.getUserId(); - // highlight-end - //.... -}); -``` - - - - -```tsx -import Hapi from "@hapi/hapi"; -import { verifySession } from "supertokens-node/recipe/session/framework/hapi"; -import { SessionRequest } from "supertokens-node/framework/hapi"; - -let server = Hapi.server({ port: 8000 }); - -server.route({ - path: "/like-comment", - method: "post", - //highlight-start - options: { - pre: [ - { - method: verifySession() - }, - ], - }, - handler: async (req: SessionRequest, res) => { - let userId = req.session!.getUserId(); - //highlight-end - //... - } -}) -``` - - - -```tsx -import Fastify from "fastify"; -import { verifySession } from "supertokens-node/recipe/session/framework/fastify"; -import { SessionRequest } from "supertokens-node/framework/fastify"; - -let fastify = Fastify(); - -//highlight-start -fastify.post("/like-comment", { - preHandler: verifySession(), -}, (req: SessionRequest, res) => { - let userId = req.session!.getUserId(); - //highlight-end - //.... -}); -``` - - - - -```tsx -import { verifySession } from "supertokens-node/recipe/session/framework/awsLambda"; -import { SessionEventV2 } from "supertokens-node/framework/awsLambda"; - -async function likeComment(awsEvent: SessionEventV2) { - let userId = awsEvent.session!.getUserId(); - //.... -}; - -//highlight-next-line -exports.handler = verifySession(likeComment); -``` - - - - -```tsx -import KoaRouter from "koa-router"; -import { verifySession } from "supertokens-node/recipe/session/framework/koa"; -import { SessionContext } from "supertokens-node/framework/koa"; - -let router = new KoaRouter(); - -//highlight-start -router.post("/like-comment", verifySession(), (ctx: SessionContext, next) => { - let userId = ctx.session!.getUserId(); - //highlight-end - //.... -}); -``` - - - - -```tsx -import { inject, intercept } from "@loopback/core"; -import { RestBindings, MiddlewareContext, post, response } from "@loopback/rest"; -import { verifySession } from "supertokens-node/recipe/session/framework/loopback"; -import { SessionContext } from "supertokens-node/framework/loopback"; - -class LikeComment { - //highlight-start - constructor(@inject(RestBindings.Http.CONTEXT) private ctx: MiddlewareContext) { } - @post("/like-comment") - @intercept(verifySession()) - @response(200) - handler() { - let userId = (this.ctx as SessionContext).session!.getUserId(); - //highlight-end - //.... - } -} -``` - - - - -```tsx -import { superTokensNextWrapper } from 'supertokens-node/nextjs' -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import { SessionRequest } from "supertokens-node/framework/express"; - -// highlight-start -export default async function likeComment(req: SessionRequest, res: any) { - await superTokensNextWrapper( - async (next) => { - await verifySession()(req, res, next); - }, - req, - res - ) - - let userId = req.session!.getUserId(); - // highlight-end - //.... -} -``` - - - - -```tsx -import { NextResponse, NextRequest } from "next/server"; -import SuperTokens from "supertokens-node"; -import { withSession } from "supertokens-node/nextjs"; -// @ts-ignore -import { backendConfig } from "@/app/config/backend"; - -SuperTokens.init(backendConfig()); - -export function POST(request: NextRequest) { - return withSession(request, async (err, session) => { - if (err) { - return NextResponse.json(err, { status: 500 }); - } - const userId = session!.getUserId(); - //.... - return NextResponse.json({}); - }); -} -``` - - - - -```tsx -import { Controller, Post, UseGuards, Session } from "@nestjs/common"; -import { SessionContainer } from "supertokens-node/recipe/session"; -// @ts-ignore -import { AuthGuard } from './auth/auth.guard'; - -@Controller() -export class ExampleController { - @Post('example') - @UseGuards(new AuthGuard()) // For more information about this guard please read our NestJS guide. - async postExample(@Session() session: SessionContainer): Promise { - //highlight-start - let userId = session.getUserId(); - - //highlight-end - //.... - return true; - } -} -``` - - - - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/supertokens/supertokens-golang/recipe/session" -) - -func main() { - _ = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - // Wrap the API handler in session.VerifySession - session.VerifySession(nil, likeCommentAPI).ServeHTTP(rw, r) - }) -} - -func likeCommentAPI(w http.ResponseWriter, r *http.Request) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(r.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/gin-gonic/gin" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" -) - -func main() { - router := gin.New() - - // Wrap the API handler in session.VerifySession - router.POST("/likecomment", verifySession(nil), likeCommentAPI) -} - -// This is a function that wraps the supertokens verification function -// to work the gin -func verifySession(options *sessmodels.VerifySessionOptions) gin.HandlerFunc { - return func(c *gin.Context) { - session.VerifySession(options, func(rw http.ResponseWriter, r *http.Request) { - c.Request = c.Request.WithContext(r.Context()) - c.Next() - })(c.Writer, c.Request) - // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly - c.Abort() - } -} - -func likeCommentAPI(c *gin.Context) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(c.Request.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/go-chi/chi" - "github.com/supertokens/supertokens-golang/recipe/session" -) - -func main() { - r := chi.NewRouter() - - // Wrap the API handler in session.VerifySession - r.Post("/likecomment", session.VerifySession(nil, likeCommentAPI)) -} - -func likeCommentAPI(w http.ResponseWriter, r *http.Request) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(r.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/gorilla/mux" - "github.com/supertokens/supertokens-golang/recipe/session" -) - -func main() { - router := mux.NewRouter() - - // Wrap the API handler in session.VerifySession - router.HandleFunc("/likecomment", session.VerifySession(nil, likeCommentAPI)).Methods(http.MethodPost) -} - -func likeCommentAPI(w http.ResponseWriter, r *http.Request) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(r.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - - - - - - -```python -from supertokens_python.recipe.session.framework.fastapi import verify_session -from supertokens_python.recipe.session import SessionContainer -from fastapi import Depends - -# highlight-start -@app.post('/like_comment') # type: ignore -async def like_comment(session: SessionContainer = Depends(verify_session())): - user_id = session.get_user_id() - # highlight-end - - print(user_id) -``` - - - - -```python -from supertokens_python.recipe.session.framework.flask import verify_session -from supertokens_python.recipe.session import SessionContainer -from flask import g - -# highlight-start -@app.route('/update-jwt', methods=['POST']) # type: ignore -@verify_session() -def like_comment(): - session: SessionContainer = g.supertokens # type: ignore - - user_id = session.get_user_id() - # highlight-end - - print(user_id) -``` - - - - -```python -from supertokens_python.recipe.session.framework.django.asyncio import verify_session -from django.http import HttpRequest -from supertokens_python.recipe.session import SessionContainer - -# highlight-start -@verify_session() -async def like_comment(request: HttpRequest): - session: SessionContainer = request.supertokens # type: ignore - - user_id = session.get_user_id() - # highlight-end - - print(user_id) -``` - - - - - - - -The `verifySession` function returns a `401` to the frontend if a session doesn't exist, or if the access token has expired, in which case, our frontend SDK automatically refreshes the session. - -In case of successful session verification, you get access to a `session` object using which you can get the user's ID, or manipulate the session information. - - - - - - - -### Microservice authentication - -For authentication between microservices on your backend, checkout the [microservice auth guide](/docs/microservice_auth/client-credentials). - - - -## Protecting frontend routes - - - - - - - - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```tsx -import Session from 'supertokens-web-js/recipe/session'; - -async function doesSessionExist() { - if (await Session.doesSessionExist()) { - // user is logged in - } else { - // user has not logged in yet - } -} -``` - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```tsx -import Session from 'supertokens-web-js-script/recipe/session'; -async function doesSessionExist() { - if (await Session.doesSessionExist()) { - // user is logged in - } else { - // user has not logged in yet - } -} -``` - - - - - - - - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```tsx -import SuperTokens from 'supertokens-react-native'; - -async function doesSessionExist() { - if (await SuperTokens.doesSessionExist()) { - // user is logged in - } else { - // user has not logged in yet - } -} -``` - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```kotlin -import android.app.Application -import com.supertokens.session.SuperTokens - -class MainApplication: Application() { - fun doesSessionExist() { - if (SuperTokens.doesSessionExist(this.applicationContext)) { - // user is logged in - } else { - // user has not logged in yet - } - } -} -``` - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```swift -import UIKit -import SuperTokensIOS - -fileprivate class ViewController: UIViewController { - func doesSessionExist() { - if SuperTokens.doesSessionExist() { - // User is logged in - } else { - // User is not logged in - } - } -} -``` - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```dart -import 'package:supertokens_flutter/supertokens.dart'; - -Future doesSessionExist() async { - return await SuperTokens.doesSessionExist(); -} -``` - - - - - - - - - - - - - - - -## See also - -- Optional sessions [for APIs](../common-customizations/sessions/session-verification-in-api/verify-session#optional-session-verification) and [the frontend](../common-customizations/sessions/checking-session-front-end) -- [Verifying session without using a middleware](../common-customizations/sessions/session-verification-in-api/get-session) -- Session claim validation [for APIs](../common-customizations/sessions/claims/claim-validators) and [the frontend](../common-customizations/sessions/claims/claim-validators) -- [Changing session lifetime](../common-customizations/sessions/change-session-timeout) -- [Sharing session across sub domains](../common-customizations/sessions/share-sessions-across-sub-domains) - - diff --git a/v2/passwordless/custom-ui/sign-out.mdx b/v2/passwordless/custom-ui/sign-out.mdx index af9169fa0..81b1a86e9 100644 --- a/v2/passwordless/custom-ui/sign-out.mdx +++ b/v2/passwordless/custom-ui/sign-out.mdx @@ -4,135 +4,9 @@ title: Implementing sign out hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" -import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" -import TabItem from '@theme/TabItem'; -import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" -# Implementing sign out + -The `signOut` method revokes the session on the frontend and on the backend. Calling this function without a valid session also yields a successful response. - - - - - - -```tsx -import Session from "supertokens-web-js/recipe/session"; - -async function logout () { - // highlight-next-line - await Session.signOut(); - window.location.href = "/auth"; // or to wherever your logic page is -} -``` - - - - -```tsx -import supertokensSession from "supertokens-web-js-script/recipe/session"; -async function logout () { - // highlight-next-line - await supertokensSession.signOut(); - window.location.href = "/auth"; // or to wherever your logic page is -} -``` - - - - - - - - - - - - -```tsx -import SuperTokens from "supertokens-react-native"; - -async function logout () { - // highlight-next-line - await SuperTokens.signOut(); - // navigate to the login screen.. -} -``` - - - - - -```kotlin -import android.app.Application -import com.supertokens.session.SuperTokens - -class MainApplication: Application() { - fun logout() { - // highlight-next-line - SuperTokens.signOut(this); - // navigate to the login screen.. - } -} -``` - - - - - -```swift -import UIKit -import SuperTokensIOS - -fileprivate class ViewController: UIViewController { - func signOut() { - SuperTokens.signOut(completionHandler: { - error in - - if error != nil { - // handle error - } else { - // Signed out successfully - } - }) - } -} -``` - - - - - -```dart -import 'package:supertokens_flutter/supertokens.dart'; - -Future signOut() async { - await SuperTokens.signOut( - completionHandler: (error) { - // handle error if any - } - ); -} -``` - - - - - - - - - -- On success, the `signOut` function does not redirect the user to another page, so you must redirect the user yourself. -- The `signOut` function calls the signout API exposed by the session recipe on the backend. -- If you call the `signOut` function whilst the access token has expired, but the refresh token still exists, our SDKs will do an automatic session refresh before revoking the session. - -## See also - -- [Revoking a session on the backend](../common-customizations/sessions/revoke-session) diff --git a/v2/passwordless/introduction.mdx b/v2/passwordless/introduction.mdx index 6d65db0bc..1b61d2fbd 100644 --- a/v2/passwordless/introduction.mdx +++ b/v2/passwordless/introduction.mdx @@ -1,48 +1,12 @@ --- id: introduction -title: Introduction & Architecture +title: Introduction hide_title: true +hide_table_of_contents: true --- -import TechStackSupport from "../community/reusableMD/supported-tech-stacks.mdx" +import Redirector from '/src/components/Redirector'; -# Passwordless login -## Features + -- Sign in / up with OTP or / and [magic link via SMS or email](https://supertokens.com/features/email-magic-links)
-- Secure session management
-- Customise email or SMS
-- Integrate with your own email / SMS sending service
- - - - - -## Demo application - -- See our [live demo app](https://^{docsLinkRecipeName}.demo.supertokens.com/). -- We have a read-only user management dashboard (with fake data), and it can be accessed [on this link](https://dashboard.demo.supertokens.com/api/auth/dashboard). The credentials for logging in are: - ```text - email: demo@supertokens.com - password: abcd1234 - ``` -- Generate a starter app - ```bash - npx create-supertokens-app@latest --recipe=^{docsLinkRecipeName} - ``` -- The user management dashboard for the starter app can be accessed on `http://localhost:3001/auth/dashboard`. You will have to first create users (instrs are on the dashboard UI), before logging in. - -## Architecture - -import Architecture from "../community/reusableMD/intro-architecture.mdx" - - - -## Next steps - -import NextSteps from "../community/reusableMD/intro-next-steps.mdx" - - - - \ No newline at end of file diff --git a/v2/passwordless/pre-built-ui/enable-email-verification.mdx b/v2/passwordless/pre-built-ui/enable-email-verification.mdx index 59afbfc19..4f3ade3f4 100644 --- a/v2/passwordless/pre-built-ui/enable-email-verification.mdx +++ b/v2/passwordless/pre-built-ui/enable-email-verification.mdx @@ -4,814 +4,8 @@ title: Enable email verification hide_title: true --- -# Enable email verification +import Redirector from '/src/components/Redirector'; -:::important -For passwordless login with email, a user's email is automatically marked as verified when they login. Therefore, the only time this flow would be triggered is if a user changes their email during a session. -::: - - - + -import CustomAdmonition from "/src/components/customAdmonition" -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"; - - -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 - - - - -```tsx -import SuperTokens from "supertokens-node"; -import EmailVerification from "supertokens-node/recipe/emailverification"; -import Session from "supertokens-node/recipe/session"; - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // highlight-start - EmailVerification.init({ - mode: "REQUIRED", // or "OPTIONAL" - }), - // highlight-end - Session.init(), - ], -}); -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/emailverification" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evmodels" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - supertokens.Init(supertokens.TypeInput{ - RecipeList: []supertokens.Recipe{ - // highlight-start - emailverification.Init(evmodels.TypeInput{ - Mode: evmodels.ModeRequired, // or evmodels.ModeOptional - }), - // highlight-end - session.Init(&sessmodels.TypeInput{}), - }, - }) -} -``` - - - - -```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import session -from supertokens_python.recipe import emailverification - -init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore - recipe_list=[ - # highlight-start - emailverification.init(mode='REQUIRED'), # or 'OPTIONAL' - # highlight-end - session.init() - ] -) -``` - - - - - - - - - - -import {Question}from "/src/components/question" - -## Step 2: Frontend setup - - - - - - - -```tsx -import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; -import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui"; -import EmailVerification from "supertokens-auth-react/recipe/emailverification"; -^{prebuiltuiimport} -import { EmailVerificationPreBuiltUI } from "supertokens-auth-react/recipe/emailverification/prebuiltui"; -import Session from "supertokens-auth-react/recipe/session"; -import reactRouterDOM, { Routes, BrowserRouter as Router, Route } from "react-router-dom"; - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // highlight-start - EmailVerification.init({ - mode: "REQUIRED", // or "OPTIONAL" - }), - // highlight-end - Session.init(), - ], -}); - -function App() { - return ( - -
- -
- - // highlight-start - {getSuperTokensRoutesForReactRouterDom(reactRouterDOM, [^{recipePreBuiltUINameCapitalLetters}, EmailVerificationPreBuiltUI])} - // highlight-end - // ... other routes - -
-
-
-
- ); -} -``` - -
- - - -```tsx -import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; -import { canHandleRoute, getRoutingComponent } from "supertokens-auth-react/ui"; -import EmailVerification from "supertokens-auth-react/recipe/emailverification"; -^{prebuiltuiimport} -import { EmailVerificationPreBuiltUI } from "supertokens-auth-react/recipe/emailverification/prebuiltui"; -import Session from "supertokens-auth-react/recipe/session"; - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // highlight-start - EmailVerification.init({ - mode: "REQUIRED", // or "OPTIONAL" - }), - // highlight-end - Session.init(), - ], -}); - -function App() { - // highlight-start - if (canHandleRoute([^{recipePreBuiltUINameCapitalLetters}, EmailVerificationPreBuiltUI])) { - return getRoutingComponent([^{recipePreBuiltUINameCapitalLetters}, EmailVerificationPreBuiltUI]) - } - // highlight-end - return ( - {/*Your app*/} - ); -} -``` - - - -
- -
- - - -```tsx -// this goes in the auth route config of your frontend app (once the pre built UI script has been loaded) -import {init as supertokensUIInit} from "supertokens-auth-react-script"; -import supertokensUIEmailVerification from "supertokens-auth-react-script/recipe/emailverification"; -import supertokensUISession from "supertokens-auth-react-script/recipe/session"; - -supertokensUIInit({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // highlight-start - supertokensUIEmailVerification.init({ - mode: "REQUIRED", // or "OPTIONAL" - }), - // highlight-end - supertokensUISession.init(), - ], -}); -``` - - - -
- - - - - - - -## Step 3: Checking if the user's email is verified in your APIs - -

If using REQUIRED mode

- -On the backend, when you initialize the email verification recipe in this mode, the `verifySession` middleware automatically checks if the user's email is verified based on the contents of the session's payload. If the email is not verified, the `verifySession` middleware will return a `403` status code to the client. - -

If using OPTIONAL mode

- -In this mode, you need to check if the email is verified yourself in the APIs in which you want this constraint. The verification status should already be in the session's payload. - - - - - - -```tsx -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import express from "express"; -import { SessionRequest } from "supertokens-node/framework/express"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let app = express(); - -app.post( - "/update-blog", - verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - }), - async (req: SessionRequest, res) => { - // All validator checks have passed and the user has a verified email address - } -); -``` - - - -```tsx -import Hapi from "@hapi/hapi"; -import { verifySession } from "supertokens-node/recipe/session/framework/hapi"; -import {SessionRequest} from "supertokens-node/framework/hapi"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let server = Hapi.server({ port: 8000 }); - -server.route({ - path: "/update-blog", - method: "post", - options: { - pre: [ - { - method: verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - }), - }, - ], - }, - handler: async (req: SessionRequest, res) => { - // All validator checks have passed and the user has a verified email address - } -}) -``` - - - -```tsx -import Fastify from "fastify"; -import { verifySession } from "supertokens-node/recipe/session/framework/fastify"; -import { SessionRequest } from "supertokens-node/framework/fastify"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let fastify = Fastify(); - -fastify.post("/update-blog", { - preHandler: verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - }), -}, async (req: SessionRequest, res) => { - // All validator checks have passed and the user has a verified email address -}); -``` - - - - -```tsx -import { verifySession } from "supertokens-node/recipe/session/framework/awsLambda"; -import { SessionEvent } from "supertokens-node/framework/awsLambda"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -async function updateBlog(awsEvent: SessionEvent) { - // All validator checks have passed and the user has a verified email address -}; - -exports.handler = verifySession(updateBlog, { - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] -}); -``` - - - - -```tsx -import KoaRouter from "koa-router"; -import { verifySession } from "supertokens-node/recipe/session/framework/koa"; -import {SessionContext} from "supertokens-node/framework/koa"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let router = new KoaRouter(); - -router.post("/update-blog", verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - }), async (ctx: SessionContext, next) => { - // All validator checks have passed and the user has a verified email address -}); -``` - - - - -```tsx -import { inject, intercept } from "@loopback/core"; -import { RestBindings, MiddlewareContext, post, response } from "@loopback/rest"; -import { verifySession } from "supertokens-node/recipe/session/framework/loopback"; -import Session from "supertokens-node/recipe/session"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -class SetRole { - constructor(@inject(RestBindings.Http.CONTEXT) private ctx: MiddlewareContext) { } - @post("/update-blog") - @intercept(verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - })) - @response(200) - async handler() { - // All validator checks have passed and the user has a verified email address - } -} -``` - - - - -```tsx -import { superTokensNextWrapper } from 'supertokens-node/nextjs' -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import { SessionRequest } from "supertokens-node/framework/express"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -// highlight-start -export default async function setRole(req: SessionRequest, res: any) { - await superTokensNextWrapper( - async (next) => { - await verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - })(req, res, next); - }, - req, - res - ) - // All validator checks have passed and the user has a verified email address -} -``` - - - - -```tsx -import SuperTokens from "supertokens-node"; -import { NextResponse, NextRequest } from "next/server"; -import { withSession } from "supertokens-node/nextjs"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; -// @ts-ignore -import { backendConfig } from "@/app/config/backend"; - -SuperTokens.init(backendConfig()); - -export async function POST(request: NextRequest) { - return withSession(request, async (err, session) => { - if (err) { - return NextResponse.json(err, { status: 500 }); - } - // All validator checks have passed and the user has a verified email address - return NextResponse.json({ message: "Your email is verified!" }); - }, - { - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - } - ); -} -``` - - - - -```tsx -import { Controller, Post, UseGuards, Request, Response, Session } from "@nestjs/common"; -import { SessionContainer, SessionClaimValidator } from "supertokens-node/recipe/session"; -// @ts-ignore -import { AuthGuard } from './auth/auth.guard'; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -@Controller() -export class ExampleController { - @Post('example') - @UseGuards(new AuthGuard({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators: SessionClaimValidator[]) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - })) - async postExample(@Session() session: SessionContainer): Promise { - // All validator checks have passed and the user has a verified email address - return true; - } -} -``` - - - - - - - - -```go -import ( - "net/http" - - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - _ = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - session.VerifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }, exampleAPI).ServeHTTP(rw, r) - }) -} - -func exampleAPI(w http.ResponseWriter, r *http.Request) { - // TODO: session is verified and all validators have passed.. -} -``` - - - - -```go -import ( - "net/http" - - "github.com/gin-gonic/gin" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - router := gin.New() - - // Wrap the API handler in session.VerifySession - router.POST("/likecomment", verifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }), exampleAPI) -} - -// This is a function that wraps the supertokens verification function -// to work the gin -func verifySession(options *sessmodels.VerifySessionOptions) gin.HandlerFunc { - return func(c *gin.Context) { - session.VerifySession(options, func(rw http.ResponseWriter, r *http.Request) { - c.Request = c.Request.WithContext(r.Context()) - c.Next() - })(c.Writer, c.Request) - // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly - c.Abort() - } -} - -func exampleAPI(c *gin.Context) { - // TODO: session is verified and all claim validators pass. -} -``` - - - - -```go -import ( - "net/http" - - "github.com/go-chi/chi" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - r := chi.NewRouter() - - // Wrap the API handler in session.VerifySession - r.Post("/likecomment", session.VerifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }, exampleAPI)) -} - -func exampleAPI(w http.ResponseWriter, r *http.Request) { - // TODO: session is verified and all claim validators pass. -} -``` - - - - -```go -import ( - "net/http" - - "github.com/gorilla/mux" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - router := mux.NewRouter() - - // Wrap the API handler in session.VerifySession - router.HandleFunc("/likecomment", session.VerifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }, exampleAPI)).Methods(http.MethodPost) -} - -func exampleAPI(w http.ResponseWriter, r *http.Request) { - // TODO: session is verified and all claim validators pass. -} -``` - - - - - - - - - -```python -from supertokens_python.recipe.session.framework.fastapi import verify_session -from supertokens_python.recipe.emailverification import EmailVerificationClaim -from supertokens_python.recipe.session import SessionContainer -from fastapi import Depends - -@app.post('/like_comment') # type: ignore -async def like_comment(session: SessionContainer = Depends( - verify_session( - # highlight-start - # We add the EmailVerificationClaim's is_verified validator - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \ - [EmailVerificationClaim.validators.is_verified()] - # highlight-end - ) -)): - # All validator checks have passed and the user has a verified email address - pass -``` - - - - -```python -from supertokens_python.recipe.session.framework.flask import verify_session -from supertokens_python.recipe.emailverification import EmailVerificationClaim - -@app.route('/update-jwt', methods=['POST']) # type: ignore -@verify_session( - # highlight-start - # We add the EmailVerificationClaim's is_verified validator - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \ - [EmailVerificationClaim.validators.is_verified()] - # highlight-end -) -def like_comment(): - # All validator checks have passed and the user has a verified email address - pass -``` - - - - -```python -from supertokens_python.recipe.session.framework.django.asyncio import verify_session -from django.http import HttpRequest -from supertokens_python.recipe.emailverification import EmailVerificationClaim - -@verify_session( - # highlight-start - # We add the EmailVerificationClaim's is_verified validator - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \ - [EmailVerificationClaim.validators.is_verified()] - # highlight-end -) -async def like_comment(request: HttpRequest): - # All validator checks have passed and the user has a verified email address - pass -``` - - - - - - - -We add the SDK's `EmailVerificationClaim` validator to the `verifySession` middleware call as shown above, and that will only allow access if the email is verified, else it will return `403` to the frontend. - - - - - - - -## Step 4: Protecting frontend routes - - - - -

If using REQUIRED mode

- -Wrapping your website routes using `` should enforce email verification. If the user's email is not verified, SuperTokens will automatically redirect the user to the email verification screen. - -

If using OPTIONAL mode

- -```tsx -import React from "react"; -import { SessionAuth, useSessionContext } from 'supertokens-auth-react/recipe/session'; -import { EmailVerificationClaim } from 'supertokens-auth-react/recipe/emailverification'; - -const VerifiedRoute = (props: React.PropsWithChildren) => { - return ( - - - {props.children} - - - ); -} - -function InvalidClaimHandler(props: React.PropsWithChildren) { - let sessionContext = useSessionContext(); - if (sessionContext.loading) { - return null; - } - - if (sessionContext.invalidClaims.some(i => i.id === EmailVerificationClaim.id)) { - // Alternatively you could redirect the user to the email verification screen to trigger the verification email - // Note: /auth/verify-email is the default email verification path - // window.location.assign("/auth/verify-email") - return
You cannot access this page because your email address is not verified.
- } - - // We show the protected route since all claims validators have - // passed implying that the user has verified their email. - return
{props.children}
; -} -``` -Above we are creating a generic component called `VerifiedRoute` which enforces that its child components can only be rendered if the user has a verified email address. - -In the `VerifiedRoute` component, we use the `SessionAuth` wrapper to ensure that the session exists. The `SessionAuth` wrapper will create a context that contains a prop called `invalidClaims` which will contain a list of all claim validations that have failed. - -The email verification recipe on the frontend, adds the `EmailVerificationClaim` validator automatically, so if the user's email is not verified, the `invalidClaims` prop will contain information about that. Alternatively you could also redirect the user to the default email verification path to trigger the sending of the verification email. - -We check the result of the validation in the `InvalidClaimHandler` component which displays `"You cannot access this page because your email address is not verified."` if the `EmailVerificationClaim` validator failed. - -If all validations pass, we render the `props.children` component. - -
- - - -```tsx -import Session from "supertokens-web-js/recipe/session"; -import { EmailVerificationClaim } from "supertokens-web-js/recipe/emailverification"; - -async function shouldLoadRoute(): Promise { - if (await Session.doesSessionExist()) { - // highlight-start - let validationErrors = await Session.validateClaims(); - - if (validationErrors.length === 0) { - // user has verified their email address - return true; - } else { - for (const err of validationErrors) { - if (err.id === EmailVerificationClaim.id) { - // email is not verified - } - } - } - // highlight-end - } - // a session does not exist, or email is not verified - return false -} -``` - -In your protected routes, you need to first check if a session exists, and then call the `Session.validateClaims` function as shown above. This function inspects the session's contents and runs claim validators on them. If a claim validator fails, it will be reflected in the `validationErrors` variable. The `EmailVerificationClaim` validator will be automatically checked by this function since you have initialized the email verification recipe. - - - -
- - - - - - - -## See also - -- [Post email verification action](../common-customizations/email-verification/handling-email-verification-success) -- [Change email verification link's lifetime](../common-customizations/email-verification/changing-token-lifetime) -- [Customise email template or email delivery method](../email-delivery/about) -- [Manually changing email verification status for a user](../common-customizations/email-verification/changing-email-verification-status) -- [Generating email verification links manually](../common-customizations/email-verification/generate-link-manually) -- [Replacing, customising or embedding the frontend UI](../common-customizations/email-verification/embed-in-page) -- 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/passwordless/pre-built-ui/handling-session-tokens.mdx b/v2/passwordless/pre-built-ui/handling-session-tokens.mdx index 69b80421f..6ff934e75 100644 --- a/v2/passwordless/pre-built-ui/handling-session-tokens.mdx +++ b/v2/passwordless/pre-built-ui/handling-session-tokens.mdx @@ -4,165 +4,7 @@ title: Handling session tokens hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -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'; + -# Handling session tokens - - - - - -:::success -No action required. -::: - -Our frontend SDK handles everything for you. You only need to make sure that you have called `supertokens.init` before making any network requests. - -Our SDK adds interceptors to `fetch` and `XHR` (used by `axios`) to save and add session tokens from and to the request. - -By default, our web SDKs use cookies to provide credentials. - - - - - -## Getting the access token - -:::caution -Our SDK automatically handles adding the access token to request headers. You only need to add the access token to the request if you want to send the access token to a different API domain than what is configured on the frontend SDK init function call. -::: - -If you are using a header-based session or enabled `exposeAccessTokenToFrontendInCookieBasedAuth` (see below), you can read the access token on the frontend using the `getAccessToken` method: - - - - - - - - -```tsx -import Session from "supertokens-auth-react/recipe/session"; - -async function getToken(): Promise { - // highlight-next-line - const accessToken = await Session.getAccessToken(); - console.log(accessToken); -} -``` - - - - - -```tsx -import Session from "supertokens-web-js/recipe/session"; - -async function getToken(): Promise { - // highlight-next-line - const accessToken = await Session.getAccessToken(); - console.log(accessToken); -} -``` - - - - - - - -### If using cookie-based sessions - -:::caution -This will expose the access token to the frontend, meaning it could be vulnerable to XSS attacks. -::: - -:::important -If you are using header-based sessions, you can skip this step -::: - -By enabling this setting, you'll expose the access token to your frontend code even if you use cookies for authentication. - - - - - - - - -```tsx -import SuperTokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; - -SuperTokens.init({ - supertokens: { - connectionURI: "..." - }, - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "..." - }, - recipeList: [ - Session.init({ - //highlight-start - exposeAccessTokenToFrontendInCookieBasedAuth: true, - //highlight-end - }) - ] -}); -``` - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - supertokens.Init(supertokens.TypeInput{ - RecipeList: []supertokens.Recipe{ - session.Init(&sessmodels.TypeInput{ - //highlight-start - ExposeAccessTokenToFrontendInCookieBasedAuth: true, - //highlight-end - }), - }, - }) -} -``` - - - -```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import session - -init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore - recipe_list=[ - session.init( - # highlight-next-line - expose_access_token_to_frontend_in_cookie_based_auth=True - ) - ] -) -``` - - - - - diff --git a/v2/passwordless/pre-built-ui/multitenant-login.mdx b/v2/passwordless/pre-built-ui/multitenant-login.mdx index 19b41a103..bf2091f6c 100644 --- a/v2/passwordless/pre-built-ui/multitenant-login.mdx +++ b/v2/passwordless/pre-built-ui/multitenant-login.mdx @@ -4,249 +4,8 @@ title: "Multitenant and Enterprise SSO login" hide_title: true --- -import AppInfoForm from "/src/components/appInfoForm" -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import PythonSyncAsyncSubTabs from "/src/components/tabs/PythonSyncAsyncSubTabs"; -import CoreVersionSubTabs from "/src/components/tabs/CoreVersionSubTabs"; -import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs"; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import MultiTenancyPaidBanner from '../../community/reusableMD/multitenancy/MultiTenancyPaidBanner.mdx' -import CoreInjector from "/src/components/coreInjector" -import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" +import Redirector from '/src/components/Redirector'; - - - - + -# Multitenant login - -Multitenant login is a feature that lets you customize the login experience for each of your customers. For example, a customer `customer1` hosted on `customer1.yourdomain.com` can have passwordless login (using this recipe), and another customer `customer2` hosted on `customer2.yourdomain.com` can have login with `Active Directory` and `Facebook` (using the thirdparty recipe). - - - - - - - -## Step 1: Create and configure a new tenant in SuperTokens core - -You can create a new tenant using our backend SDKs or via a `cURL` command to the core. - - - - - - - - - - - -```tsx -import Multitenancy from "supertokens-node/recipe/multitenancy"; -import { FactorIds } from "supertokens-node/recipe/multifactorauth" - -async function createNewTenant() { - - // highlight-start - let resp = await Multitenancy.createOrUpdateTenant("customer1", { - firstFactors: [FactorIds.OTP_PHONE, FactorIds.OTP_EMAIL, FactorIds.LINK_PHONE, FactorIds.LINK_EMAIL] - }); - // highlight-end - - if (resp.createdNew) { - // new tenant was created - } else { - // existing tenant's config was modified. - } -} -``` - -In the example above, we have added all factor IDs related to the passwordless recipe. However, you can choose to use a subset of them. The factor IDs are described below: - -- Passwordless: - - With email OTP: `FactorIds.OTP_EMAIL` - - With SMS OTP: `FactorIds.OTP_PHONE` - - With email magic link: `FactorIds.LINK_EMAIL` - - With SMS magic link: `FactorIds.LINK_PHONE` - - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/multitenancy" - "github.com/supertokens/supertokens-golang/recipe/multitenancy/multitenancymodels" -) - -func main() { - tenantId := "customer1" - passwordlessEnabled := true - - // highlight-start - resp, err := multitenancy.CreateOrUpdateTenant(tenantId, multitenancymodels.TenantConfig{ - PasswordlessEnabled: &passwordlessEnabled, - }) - // highlight-end - - if err != nil { - // handle error - } - if resp.OK.CreatedNew { - // new tenant was created - } else { - // existing tenant's config was modified. - } -} -``` - - - - - - - -```python -from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate -from supertokens_python.recipe.multifactorauth.types import FactorIds - - -async def some_func(): - response = await create_or_update_tenant( - "customer1", - TenantConfigCreateOrUpdate( - first_factors=[ - FactorIds.OTP_PHONE, - FactorIds.OTP_EMAIL, - FactorIds.LINK_PHONE, - FactorIds.LINK_EMAIL, - ] - ), - ) - - if response.status != "OK": - print("Handle error") - elif response.created_new: - print("new tenant was created") - else: - print("existing tenant's config was modified.") -``` - - - - - -```python -from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate -from supertokens_python.recipe.multifactorauth.types import FactorIds - - -async def some_func(): - response = create_or_update_tenant( - "customer1", - TenantConfigCreateOrUpdate( - first_factors=[ - FactorIds.OTP_PHONE, - FactorIds.OTP_EMAIL, - FactorIds.LINK_PHONE, - FactorIds.LINK_EMAIL, - ] - ), - ) - - if response.status != "OK": - print("Handle error") - elif response.created_new: - print("new tenant was created") - else: - print("existing tenant's config was modified.") -``` - - - - - - - - - - - - - - -```bash -curl --location --request PUT '^{coreInjector_uri_without_quotes}/recipe/multitenancy/tenant/v2' \ ---header 'api-key: ^{coreInjector_api_key_without_quotes}' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "tenantId": "customer1", - "firstFactors": ["otp-email", "otp-phone", "link-email", "link-phone"] -}' -``` - -In the example above, we have added all factor IDs related to the passwordless recipe. However, you can choose to use a subset of them. The factor IDs are described below: - -- Passwordless: - - With email OTP: `otp-email` - - With SMS OTP: `otp-phone` - - With email magic link: `link-email` - - With SMS magic link: `link-phone` - - - - - - -```bash -curl --location --request PUT '^{coreInjector_uri_without_quotes}/recipe/multitenancy/tenant' \ ---header 'api-key: ^{coreInjector_api_key_without_quotes}' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "tenantId": "customer1", - "passwordlessEnabled": true -}' -``` - - - - - - - - - - - -Create Tenant - - - - - - - - - - - -## Step 2: Build your multi tenant a UX flow - -The most common multi tenant flows are: -- [Tenants use a common domain to login](../common-customizations/multi-tenancy/common-domain-login): All tenants login using the same page (for example, `example.com/auth`) and are optionally redirected to their sub domain post login. At the start of the login flow, the customer will have to input their tenantId / workspace URL / identifier - as defined by you, and the login methods shown would be based on their tenantId. -- [Tenants use their sub domain to login](../common-customizations/multi-tenancy/sub-domain-login): Here, each tenant has a sub domain assigned to them (for example `customer1.example.com`, `customer2.example.com`, ...), and they would visit their sub domain to login and access their app. Each sub domain's login experience may be different (as defined by you or the tenant). - -SuperTokens is flexible enough to allow other forms of UX as well, but since the above two flow are most common, we provide dedicated docs for them (see the links above). - -## See also - -- Multi tenant [single domain](../common-customizations/multi-tenancy/common-domain-login) or [sub domain](../common-customizations/multi-tenancy/sub-domain-login) login. -- [Tenant data isolation](../common-customizations/multi-tenancy/new-tenant-config#providing-additional-configuration-per-tenant). - - diff --git a/v2/passwordless/pre-built-ui/securing-routes.mdx b/v2/passwordless/pre-built-ui/securing-routes.mdx index a45ea028c..adf124a95 100644 --- a/v2/passwordless/pre-built-ui/securing-routes.mdx +++ b/v2/passwordless/pre-built-ui/securing-routes.mdx @@ -4,530 +4,8 @@ title: "Securing your API and frontend routes" hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -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 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 + -## Protecting APIs - - - - - - - -### Requiring an active session - -For your APIs that require a user to be logged in, use the `verifySession` middleware - - - - - - -```tsx -import express from "express"; -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import { SessionRequest } from "supertokens-node/framework/express"; - -let app = express(); - -// highlight-start -app.post("/like-comment", verifySession(), (req: SessionRequest, res) => { - let userId = req.session!.getUserId(); - // highlight-end - //.... -}); -``` - - - - -```tsx -import Hapi from "@hapi/hapi"; -import { verifySession } from "supertokens-node/recipe/session/framework/hapi"; -import { SessionRequest } from "supertokens-node/framework/hapi"; - -let server = Hapi.server({ port: 8000 }); - -server.route({ - path: "/like-comment", - method: "post", - //highlight-start - options: { - pre: [ - { - method: verifySession() - }, - ], - }, - handler: async (req: SessionRequest, res) => { - let userId = req.session!.getUserId(); - //highlight-end - //... - } -}) -``` - - - -```tsx -import Fastify from "fastify"; -import { verifySession } from "supertokens-node/recipe/session/framework/fastify"; -import { SessionRequest } from "supertokens-node/framework/fastify"; - -let fastify = Fastify(); - -//highlight-start -fastify.post("/like-comment", { - preHandler: verifySession(), -}, (req: SessionRequest, res) => { - let userId = req.session!.getUserId(); - //highlight-end - //.... -}); -``` - - - - -```tsx -import { verifySession } from "supertokens-node/recipe/session/framework/awsLambda"; -import { SessionEventV2 } from "supertokens-node/framework/awsLambda"; - -async function likeComment(awsEvent: SessionEventV2) { - let userId = awsEvent.session!.getUserId(); - //.... -}; - -//highlight-next-line -exports.handler = verifySession(likeComment); -``` - - - - -```tsx -import KoaRouter from "koa-router"; -import { verifySession } from "supertokens-node/recipe/session/framework/koa"; -import { SessionContext } from "supertokens-node/framework/koa"; - -let router = new KoaRouter(); - -//highlight-start -router.post("/like-comment", verifySession(), (ctx: SessionContext, next) => { - let userId = ctx.session!.getUserId(); - //highlight-end - //.... -}); -``` - - - - -```tsx -import { inject, intercept } from "@loopback/core"; -import { RestBindings, MiddlewareContext, post, response } from "@loopback/rest"; -import { verifySession } from "supertokens-node/recipe/session/framework/loopback"; -import { SessionContext } from "supertokens-node/framework/loopback"; - -class LikeComment { - //highlight-start - constructor(@inject(RestBindings.Http.CONTEXT) private ctx: MiddlewareContext) { } - @post("/like-comment") - @intercept(verifySession()) - @response(200) - handler() { - let userId = (this.ctx as SessionContext).session!.getUserId(); - //highlight-end - //.... - } -} -``` - - - - -```tsx -import { superTokensNextWrapper } from 'supertokens-node/nextjs' -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import { SessionRequest } from "supertokens-node/framework/express"; - -// highlight-start -export default async function likeComment(req: SessionRequest, res: any) { - await superTokensNextWrapper( - async (next) => { - await verifySession()(req, res, next); - }, - req, - res - ) - - let userId = req.session!.getUserId(); - // highlight-end - //.... -} -``` - - - - -```tsx -import { NextResponse, NextRequest } from "next/server"; -import SuperTokens from "supertokens-node"; -import { withSession } from "supertokens-node/nextjs"; -// @ts-ignore -import { backendConfig } from "@/app/config/backend"; - -SuperTokens.init(backendConfig()); - -export function POST(request: NextRequest) { - return withSession(request, async (err, session) => { - if (err) { - return NextResponse.json(err, { status: 500 }); - } - const userId = session!.getUserId(); - //.... - return NextResponse.json({}); - }); -} -``` - - - - -```tsx -import { Controller, Post, UseGuards, Session } from "@nestjs/common"; -import { SessionContainer } from "supertokens-node/recipe/session"; -// @ts-ignore -import { AuthGuard } from './auth/auth.guard'; - -@Controller() -export class ExampleController { - @Post('example') - @UseGuards(new AuthGuard()) // For more information about this guard please read our NestJS guide. - async postExample(@Session() session: SessionContainer): Promise { - //highlight-start - let userId = session.getUserId(); - - //highlight-end - //.... - return true; - } -} -``` - - - - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/supertokens/supertokens-golang/recipe/session" -) - -func main() { - _ = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - // Wrap the API handler in session.VerifySession - session.VerifySession(nil, likeCommentAPI).ServeHTTP(rw, r) - }) -} - -func likeCommentAPI(w http.ResponseWriter, r *http.Request) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(r.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/gin-gonic/gin" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" -) - -func main() { - router := gin.New() - - // Wrap the API handler in session.VerifySession - router.POST("/likecomment", verifySession(nil), likeCommentAPI) -} - -// This is a function that wraps the supertokens verification function -// to work the gin -func verifySession(options *sessmodels.VerifySessionOptions) gin.HandlerFunc { - return func(c *gin.Context) { - session.VerifySession(options, func(rw http.ResponseWriter, r *http.Request) { - c.Request = c.Request.WithContext(r.Context()) - c.Next() - })(c.Writer, c.Request) - // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly - c.Abort() - } -} - -func likeCommentAPI(c *gin.Context) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(c.Request.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/go-chi/chi" - "github.com/supertokens/supertokens-golang/recipe/session" -) - -func main() { - r := chi.NewRouter() - - // Wrap the API handler in session.VerifySession - r.Post("/likecomment", session.VerifySession(nil, likeCommentAPI)) -} - -func likeCommentAPI(w http.ResponseWriter, r *http.Request) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(r.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/gorilla/mux" - "github.com/supertokens/supertokens-golang/recipe/session" -) - -func main() { - router := mux.NewRouter() - - // Wrap the API handler in session.VerifySession - router.HandleFunc("/likecomment", session.VerifySession(nil, likeCommentAPI)).Methods(http.MethodPost) -} - -func likeCommentAPI(w http.ResponseWriter, r *http.Request) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(r.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - - - - - - -```python -from supertokens_python.recipe.session.framework.fastapi import verify_session -from supertokens_python.recipe.session import SessionContainer -from fastapi import Depends - -# highlight-start -@app.post('/like_comment') # type: ignore -async def like_comment(session: SessionContainer = Depends(verify_session())): - user_id = session.get_user_id() - # highlight-end - - print(user_id) -``` - - - - -```python -from supertokens_python.recipe.session.framework.flask import verify_session -from supertokens_python.recipe.session import SessionContainer -from flask import g - -# highlight-start -@app.route('/update-jwt', methods=['POST']) # type: ignore -@verify_session() -def like_comment(): - session: SessionContainer = g.supertokens # type: ignore - - user_id = session.get_user_id() - # highlight-end - - print(user_id) -``` - - - - -```python -from supertokens_python.recipe.session.framework.django.asyncio import verify_session -from django.http import HttpRequest -from supertokens_python.recipe.session import SessionContainer - -# highlight-start -@verify_session() -async def like_comment(request: HttpRequest): - session: SessionContainer = request.supertokens # type: ignore - - user_id = session.get_user_id() - # highlight-end - - print(user_id) -``` - - - - - - - -The `verifySession` function returns a `401` to the frontend if a session doesn't exist, or if the access token has expired, in which case, our frontend SDK automatically refreshes the session. - -In case of successful session verification, you get access to a `session` object using which you can get the user's ID, or manipulate the session information. - - - - - - - -### Microservice authentication - -For authentication between microservices on your backend, checkout the [microservice auth guide](/docs/microservice_auth/client-credentials). - - - -## Protecting website routes - - - - - -:::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. - -::: - - - - -You can wrap your components with the `` react component. This will ensure that your component renders only if the user is logged in. If they are not logged in, the user will be redirected to the login page. - -```tsx -import React from "react"; -import { - BrowserRouter, - Routes, - Route, -} from "react-router-dom"; -// highlight-next-line -import { SessionAuth } from "supertokens-auth-react/recipe/session"; -// @ts-ignore -import MyDashboardComponent from "./dashboard"; - -class App extends React.Component { - render() { - return ( - - - - {/*Components that require to be protected by authentication*/} - - - // highlight-end - } /> - - - ); - } -} -``` - - - - - -You can use the `doesSessionExist` function to check if a session exists in all your routes. - -```tsx -import Session from 'supertokens-web-js/recipe/session'; - -async function doesSessionExist() { - if (await Session.doesSessionExist()) { - // user is logged in - } else { - // user has not logged in yet - } -} -``` - - - - - - - - - - - -## See also - -- Optional sessions [for APIs](../common-customizations/sessions/session-verification-in-api/verify-session#optional-session-verification) and [the frontend](../common-customizations/sessions/checking-session-front-end) -- [Verifying session without using a middleware](../common-customizations/sessions/session-verification-in-api/get-session) -- Session claim validation [for APIs](../common-customizations/sessions/claims/claim-validators) and [the frontend](../common-customizations/sessions/claims/claim-validators) -- [Changing session lifetime](../common-customizations/sessions/change-session-timeout) -- [Sharing session across sub domains](../common-customizations/sessions/share-sessions-across-sub-domains) - - diff --git a/v2/passwordless/pre-built-ui/setup/backend.mdx b/v2/passwordless/pre-built-ui/setup/backend.mdx index bfed7ace0..577fc5a32 100644 --- a/v2/passwordless/pre-built-ui/setup/backend.mdx +++ b/v2/passwordless/pre-built-ui/setup/backend.mdx @@ -4,973 +4,8 @@ title: "Step 2: Backend" hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import TabItem from '@theme/TabItem'; -import NodeJSFrameworkSubTabsServerless from "/src/components/tabs/NodeJSFrameworkSubTabsServerless" -import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs" -import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" -import AppInfoForm from "/src/components/appInfoForm" -import { PasswordlessBackendForm } from "/src/components/snippetConfigForm/passwordlessBackendForm"; -import CoreInjector from "/src/components/coreInjector" -import { Question, Answer }from "/src/components/question" -import BackendSDKCasing from "/src/components/BackendSDKCasing" -import BackendDeliveryMethod from "../../reusableMD/backendDeliveryMethod.mdx" -# Backend Integration + -## Supported frameworks - -import TechStackSupport from "../../../community/reusableMD/supported-tech-stacks-backend.mdx" - - - -## 1) Install - - - - -```bash -npm i -s supertokens-node -``` - - - - -```bash -go get github.com/supertokens/supertokens-golang -``` - - - - -```bash -pip install supertokens-python -``` - - - - -## 2) Initialise SuperTokens - - - - - - -Add the code below to your server's init file. - - - - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import Passwordless from "supertokens-node/recipe/passwordless"; - -supertokens.init({ - framework: "express", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - Passwordless.init({ - flowType: "^{form_flowType}", - contactMethod: "^{form_contactMethod}" - }), - Session.init() // initializes session features - ] -}); -``` - - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import Passwordless from "supertokens-node/recipe/passwordless"; - -supertokens.init({ - framework: "hapi", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - Passwordless.init({ - flowType: "^{form_flowType}", - contactMethod: "^{form_contactMethod}" - }), - Session.init() // initializes session features - ] -}); -``` - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import Passwordless from "supertokens-node/recipe/passwordless"; - -supertokens.init({ - framework: "fastify", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - Passwordless.init({ - flowType: "^{form_flowType}", - contactMethod: "^{form_contactMethod}" - }), - Session.init() // initializes session features - ] -}); -``` - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import Passwordless from "supertokens-node/recipe/passwordless"; - -supertokens.init({ - framework: "koa", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - Passwordless.init({ - flowType: "^{form_flowType}", - contactMethod: "^{form_contactMethod}" - }), - Session.init() // initializes session features - ] -}); -``` - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import Passwordless from "supertokens-node/recipe/passwordless"; - -supertokens.init({ - framework: "loopback", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - Passwordless.init({ - flowType: "^{form_flowType}", - contactMethod: "^{form_contactMethod}" - }), - Session.init() // initializes session features - ] -}); -``` - - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/passwordless" - "github.com/supertokens/supertokens-golang/recipe/passwordless/plessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - apiBasePath := "^{form_apiBasePath}" - websiteBasePath := "^{form_websiteBasePath}" - err := supertokens.Init(supertokens.TypeInput{ - Supertokens: &supertokens.ConnectionInfo{ - ^{coreInjector_connection_uri_comment} - ConnectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}APIKey: ^{coreInjector_api_key}, - }, - AppInfo: supertokens.AppInfo{ - AppName: "^{form_appName}", - APIDomain: "^{form_apiDomain}", - WebsiteDomain: "^{form_websiteDomain}", - APIBasePath: &apiBasePath, - WebsiteBasePath: &websiteBasePath, - }, - RecipeList: []supertokens.Recipe{ - passwordless.Init(plessmodels.TypeInput{ - FlowType: "^{form_flowType}", - ^{form_contactMethod_sendCB_Go} - }), - session.Init(nil), // initializes session features - }, - }) - - if err != nil { - panic(err.Error()) - } -} -``` - - - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig -from supertokens_python.recipe import passwordless, session - -^{form_contactMethod_import_Python} -init( - app_info=InputAppInfo( - app_name="^{form_appName}", - api_domain="^{form_apiDomain}", - website_domain="^{form_websiteDomain}", - api_base_path="^{form_apiBasePath}", - website_base_path="^{form_websiteBasePath}" - ), - supertokens_config=SupertokensConfig( - ^{coreInjector_connection_uri_comment_with_hash} - connection_uri=^{coreInjector_uri} - ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} - ), - framework='fastapi', - recipe_list=[ - session.init(), # initializes session features - passwordless.init( - flow_type="^{form_flowType}", - contact_config=^{form_contactMethod_initialize_Python}() - ) - ], - mode='asgi' # use wsgi if you are running using gunicorn -) -``` - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig -from supertokens_python.recipe import passwordless, session - -^{form_contactMethod_import_Python} -init( - app_info=InputAppInfo( - app_name="^{form_appName}", - api_domain="^{form_apiDomain}", - website_domain="^{form_websiteDomain}", - api_base_path="^{form_apiBasePath}", - website_base_path="^{form_websiteBasePath}" - ), - supertokens_config=SupertokensConfig( - ^{coreInjector_connection_uri_comment_with_hash} - connection_uri=^{coreInjector_uri} - ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} - ), - framework='flask', - recipe_list=[ - session.init(), # initializes session features - passwordless.init( - flow_type="^{form_flowType}", - contact_config=^{form_contactMethod_initialize_Python}() - ) - ] -) -``` - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig -from supertokens_python.recipe import passwordless, session - -^{form_contactMethod_import_Python} -init( - app_info=InputAppInfo( - app_name="^{form_appName}", - api_domain="^{form_apiDomain}", - website_domain="^{form_websiteDomain}", - api_base_path="^{form_apiBasePath}", - website_base_path="^{form_websiteBasePath}" - ), - supertokens_config=SupertokensConfig( - ^{coreInjector_connection_uri_comment_with_hash} - connection_uri=^{coreInjector_uri} - ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} - ), - framework='django', - recipe_list=[ - session.init(), # initializes session features - passwordless.init( - flow_type="^{form_flowType}", - contact_config=^{form_contactMethod_initialize_Python}() - ) - ], - mode='asgi' # use wsgi if you are running django server in sync mode -) -``` - - - - - - - - - - - - -## 3) Add the SuperTokens APIs & CORS setup - - - - - - - - -:::important -- Add the `middleware` BEFORE all your routes. -- Add the `cors` middleware BEFORE the SuperTokens middleware as shown below. -::: - -```tsx -import express from "express"; -import cors from "cors"; -import supertokens from "supertokens-node"; -import {middleware} from "supertokens-node/framework/express"; - -let app = express(); - -app.use(cors({ - // highlight-start - origin: "^{form_websiteDomain}", - allowedHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], - credentials: true, - // highlight-end -})); - -// IMPORTANT: CORS should be before the below line. -// highlight-next-line -app.use(middleware()); - -// ...your API routes -``` -This `middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): -- `POST ^{form_apiBasePath}/signinup/code`: For starting the passwordless login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/resend`: To generate and resend a code during an already started login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/consume`: For finishing the passwordless login/sign up process -- `GET ^{form_apiBasePath}/passwordless/email/exists`: To check if an email is already signed up -- `GET ^{form_apiBasePath}/passwordless/phonenumber/exists`: To check if a phonenumber is already signed up - - - - -Register the `plugin`. - -```tsx -import Hapi from "@hapi/hapi"; -import supertokens from "supertokens-node"; -import { plugin } from "supertokens-node/framework/hapi"; - -const server = Hapi.server({ - port: 8000, - routes: { - // highlight-start - cors: { - origin: ["^{form_websiteDomain}"], - additionalHeaders: [...supertokens.getAllCORSHeaders()], - credentials: true, - } - // highlight-end - } -}); - -(async() => { - // highlight-next-line - await server.register(plugin); - - await server.start(); -})(); - -// ...your API routes -``` -This `plugin` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)) as well take care of all the errors thrown by the Supertokens library: -- `POST ^{form_apiBasePath}/signinup/code`: For starting the passwordless login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/resend`: To generate and resend a code during an already started login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/consume`: For finishing the passwordless login/sign up process -- `GET ^{form_apiBasePath}/passwordless/email/exists`: To check if an email is already signed up -- `GET ^{form_apiBasePath}/passwordless/phonenumber/exists`: To check if a phonenumber is already signed up - - - - - -Register the `plugin`. Also register [`@fastify/formbody`](https://github.com/fastify/fastify-formbody) plugin. - -```tsx -import cors from "@fastify/cors"; -import supertokens from "supertokens-node"; -import { plugin } from "supertokens-node/framework/fastify"; -import formDataPlugin from "@fastify/formbody"; - -import fastifyImport from "fastify"; -let fastify = fastifyImport(); - -// ...other middlewares -(async () => { - // highlight-start - await fastify.register(cors, { - origin: "^{form_websiteDomain}", - allowedHeaders: ['Content-Type', ...supertokens.getAllCORSHeaders()], - credentials: true, - }) - // highlight-end - // highlight-next-line - await fastify.register(formDataPlugin); - // highlight-next-line - await fastify.register(plugin); - - await fastify.listen(3000); -})(); - -// ...your API routes -``` -This `plugin` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): -- `POST ^{form_apiBasePath}/signinup/code`: For starting the passwordless login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/resend`: To generate and resend a code during an already started login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/consume`: For finishing the passwordless login/sign up process -- `GET ^{form_apiBasePath}/passwordless/email/exists`: To check if an email is already signed up -- `GET ^{form_apiBasePath}/passwordless/phonenumber/exists`: To check if a phonenumber is already signed up - - - - - -:::important -Add the `middleware` BEFORE all your routes. -::: - -```tsx -import Koa from "koa"; -import cors from '@koa/cors'; -import supertokens from "supertokens-node"; -import { middleware } from "supertokens-node/framework/koa"; - -let app = new Koa(); - -// ...other middlewares -app.use(cors({ - // highlight-start - origin: "^{form_websiteDomain}", - allowHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], - credentials: true, - // highlight-end -})); - -// highlight-next-line -app.use(middleware()); - -// ...your API routes -``` -This `middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): -- `POST ^{form_apiBasePath}/signinup/code`: For starting the passwordless login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/resend`: To generate and resend a code during an already started login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/consume`: For finishing the passwordless login/sign up process -- `GET ^{form_apiBasePath}/passwordless/email/exists`: To check if an email is already signed up -- `GET ^{form_apiBasePath}/passwordless/phonenumber/exists`: To check if a phonenumber is already signed up - - - - - -:::important -Add the `middleware` BEFORE all your routes. -::: - -```tsx -import {RestApplication} from '@loopback/rest'; -import supertokens from "supertokens-node"; -import {middleware} from "supertokens-node/framework/loopback"; - -let app = new RestApplication({ - rest: { - cors: { - // highlight-start - origin: "^{form_websiteDomain}", - allowedHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], - credentials: true - // highlight-end - } - } -}); - -// highlight-next-line -app.middleware(middleware); - -// ...your API routes -``` -This `middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): -- `POST ^{form_apiBasePath}/signinup/code`: For starting the passwordless login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/resend`: To generate and resend a code during an already started login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/consume`: For finishing the passwordless login/sign up process -- `GET ^{form_apiBasePath}/passwordless/email/exists`: To check if an email is already signed up -- `GET ^{form_apiBasePath}/passwordless/phonenumber/exists`: To check if a phonenumber is already signed up - - - - - - - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "net/http" - "strings" - - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - //highlight-start - http.ListenAndServe("SERVER ADDRESS", corsMiddleware( - supertokens.Middleware(http.HandlerFunc(func(rw http.ResponseWriter, - //highlight-end - r *http.Request) { - // TODO: Handle your APIs.. - - })))) -} - -func corsMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(response http.ResponseWriter, r *http.Request) { - response.Header().Set("Access-Control-Allow-Origin", "^{form_websiteDomain}") - response.Header().Set("Access-Control-Allow-Credentials", "true") - if r.Method == "OPTIONS" { - // we add content-type + other headers used by SuperTokens - response.Header().Set("Access-Control-Allow-Headers", - strings.Join(append([]string{"Content-Type"}, - //highlight-start - supertokens.GetAllCORSHeaders()...), ",")) - //highlight-end - response.Header().Set("Access-Control-Allow-Methods", "*") - response.Write([]byte("")) - } else { - next.ServeHTTP(response, r) - } - }) -} -``` - - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "net/http" - - "github.com/gin-contrib/cors" - "github.com/gin-gonic/gin" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - router := gin.New() - - // CORS - router.Use(cors.New(cors.Config{ - AllowOrigins: []string{"^{form_websiteDomain}"}, - AllowMethods: []string{"GET", "POST", "DELETE", "PUT", "OPTIONS"}, - AllowHeaders: append([]string{"content-type"}, - // highlight-next-line - supertokens.GetAllCORSHeaders()...), - AllowCredentials: true, - })) - - // Adding the SuperTokens middleware - // highlight-start - router.Use(func(c *gin.Context) { - supertokens.Middleware(http.HandlerFunc( - func(rw http.ResponseWriter, r *http.Request) { - c.Next() - })).ServeHTTP(c.Writer, c.Request) - // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly - c.Abort() - }) - // highlight-end - - // Add APIs and start server -} -``` - - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "github.com/go-chi/chi" - "github.com/go-chi/cors" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - r := chi.NewRouter() - - // CORS - r.Use(cors.Handler(cors.Options{ - AllowedOrigins: []string{"^{form_websiteDomain}"}, - AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, - AllowedHeaders: append([]string{"Content-Type"}, - //highlight-next-line - supertokens.GetAllCORSHeaders()...), - AllowCredentials: true, - })) - - // SuperTokens Middleware - //highlight-next-line - r.Use(supertokens.Middleware) - - // Add APIs and start server -} -``` - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "net/http" - - "github.com/gorilla/handlers" - "github.com/gorilla/mux" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - // Add APIs - - router := mux.NewRouter() - - // Adding handlers.CORS(options)(supertokens.Middleware(router))) - //highlight-start - http.ListenAndServe("SERVER ADDRESS", handlers.CORS( - handlers.AllowedHeaders(append([]string{"Content-Type"}, - supertokens.GetAllCORSHeaders()...)), - handlers.AllowedMethods([]string{"GET", "POST", "PUT", "HEAD", "OPTIONS"}), - handlers.AllowedOrigins([]string{"^{form_websiteDomain}"}), - handlers.AllowCredentials(), - )(supertokens.Middleware(router))) - //highlight-end -} -``` - - - -This `Middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): -- `POST ^{form_apiBasePath}/signinup/code`: For starting the passwordless login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/resend`: To generate and resend a code during an already started login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/consume`: For finishing the passwordless login/sign up process -- `GET ^{form_apiBasePath}/passwordless/email/exists`: To check if an email is already signed up -- `GET ^{form_apiBasePath}/passwordless/phonenumber/exists`: To check if a phonenumber is already signed up - - - - - - - - -Use the `Middleware` (**BEFORE all your routes**) and the `get_all_cors_headers()` functions as shown below. - -```python -from supertokens_python import get_all_cors_headers -from fastapi import FastAPI -from starlette.middleware.cors import CORSMiddleware -from supertokens_python.framework.fastapi import get_middleware - -app = FastAPI() -# highlight-next-line -app.add_middleware(get_middleware()) - -# TODO: Add APIs - -app.add_middleware( - CORSMiddleware, - allow_origins=[ - "^{form_websiteDomain}" - ], - allow_credentials=True, - allow_methods=["GET", "PUT", "POST", "DELETE", "OPTIONS", "PATCH"], - # highlight-next-line - allow_headers=["Content-Type"] + get_all_cors_headers(), -) - -# TODO: start server -``` - - - - -- Use the `Middleware` (**BEFORE all your routes and after calling init function**) and the `get_all_cors_headers()` functions as shown below. -- Add a route to catch all paths and return a 404. This is needed because if we don't add this, then OPTIONS request for the APIs exposed by the `Middleware` will return a `404`. - -```python -from supertokens_python import get_all_cors_headers -from flask import Flask, abort -from flask_cors import CORS # type: ignore -from supertokens_python.framework.flask import Middleware - -app = Flask(__name__) -# highlight-next-line -Middleware(app) - -# TODO: Add APIs - -CORS( - app=app, - origins=[ - "^{form_websiteDomain}" - ], - supports_credentials=True, - # highlight-next-line - allow_headers=["Content-Type"] + get_all_cors_headers(), -) - -# This is required since if this is not there, then OPTIONS requests for -# the APIs exposed by the supertokens' Middleware will return a 404 -# highlight-start -@app.route('/', defaults={'u_path': ''}) # type: ignore -@app.route('/') # type: ignore -def catch_all(u_path: str): - abort(404) -# highlight-end - -# TODO: start server -``` - - - - -Use the `Middleware` and the `get_all_cors_headers()` functions as shown below in your `settings.py`. - -```python -from supertokens_python import get_all_cors_headers -from typing import List -from corsheaders.defaults import default_headers - -CORS_ORIGIN_WHITELIST = [ - "^{form_websiteDomain}" -] - -CORS_ALLOW_CREDENTIALS = True - -CORS_ALLOWED_ORIGINS = [ - "^{form_websiteDomain}" -] - -CORS_ALLOW_HEADERS: List[str] = list(default_headers) + [ - "Content-Type" - # highlight-next-line -] + get_all_cors_headers() - -INSTALLED_APPS = [ - 'corsheaders', - 'supertokens_python' -] - -MIDDLEWARE = [ - 'corsheaders.middleware.CorsMiddleware', - ..., - # highlight-next-line - 'supertokens_python.framework.django.django_middleware.middleware', -] -``` - - - - -This `Middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): -- `POST ^{form_apiBasePath}/signinup/code`: For starting the passwordless login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/resend`: To generate and resend a code during an already started login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/consume`: For finishing the passwordless login/sign up process -- `GET ^{form_apiBasePath}/passwordless/email/exists`: To check if an email is already signed up -- `GET ^{form_apiBasePath}/passwordless/phonenumber/exists`: To check if a phonenumber is already signed up - - - - - - - -## 4) Add the SuperTokens error handler -Add the `errorHandler` **AFTER all your routes**, but **BEFORE your error handler** - - - - - - -```tsx -import express from "express"; -import {errorHandler} from "supertokens-node/framework/express"; - -const app = express(); -// ...your API routes - -// highlight-start -// Add this AFTER all your routes -app.use(errorHandler()) -// highlight-end - -// your own error handler -app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => { - // TODO -}); - -``` - - - -No additional `errorHandler` is required. - - - - -Add the `errorHandler` **Before all your routes and plugin registration** - -```tsx -import Fastify from "fastify"; -import {errorHandler} from "supertokens-node/framework/fastify"; - -const fastify = Fastify(); - -// highlight-next-line -fastify.setErrorHandler(errorHandler()); - -// ...your API routes - -``` - - - -No additional `errorHandler` is required. - - - - -No additional `errorHandler` is required. - - - - - - -:::info -You can skip this step -::: - - - - -:::info -You can skip this step -::: - - - - -## 5) Setup the SuperTokens core - -You need to now setup an instance of the SuperTokens core for your app (that your backend should connect to). You have two options: -- [Managed service](./core/saas-setup) -- Self hosted with your own database ([With Docker](./core/with-docker) or [Without Docker](./core/without-docker)) \ No newline at end of file diff --git a/v2/passwordless/pre-built-ui/setup/frontend.mdx b/v2/passwordless/pre-built-ui/setup/frontend.mdx index baf8a64fb..bac6ce88b 100644 --- a/v2/passwordless/pre-built-ui/setup/frontend.mdx +++ b/v2/passwordless/pre-built-ui/setup/frontend.mdx @@ -3,670 +3,9 @@ id: frontend title: "Step 1: Frontend" hide_title: true --- -import { PasswordlessFrontendForm } from "/src/components/snippetConfigForm/passwordlessFrontendForm"; - - - +import Redirector from '/src/components/Redirector'; -import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" -import TabItem from '@theme/TabItem'; -import {Question, Answer}from "/src/components/question" -import AppInfoForm from "/src/components/appInfoForm" -import RRDVersionSubTabs from "/src/components/tabs/RRDVersionSubTabs" -import NpmVersionOrYarnSubTabs from "/src/components/tabs/NpmVersionOrYarnSubTabs" -import PreBuiltUIInjector from "/src/components/prebuiltuiInjector" -# Frontend Integration + -## Supported frameworks - -import TechStackSupport from "../../../community/reusableMD/supported-tech-stacks-frontend.mdx" - - - -# Automatic setup using CLI - -Run the following command in your terminal. -```bash -npx create-supertokens-app@latest --recipe=^{docsLinkRecipeName} -``` - -Once this is done, you can skip Step (1) and (2) in this section (see left nav bar) and move directly to setting up the SuperTokens core (Step 3). - -Or, you can manually integrate SuperTokens by following the steps below. - -# Manual setup steps below - -## 1) Install - - - - - - - - -```bash -npm i -s supertokens-auth-react -``` - - - - -```bash -npm i -s supertokens-auth-react supertokens-web-js -``` - - - - -```bash -yarn add supertokens-auth-react supertokens-web-js -``` - - - - - - - - - -Start by installing the SuperTokens Web SDK: - -```bash -npm i -s supertokens-web-js -``` - - - - - -Start by installing the SuperTokens Web SDK: - -```bash -npm i -s supertokens-web-js -``` - - - - - -Start by installing the SuperTokens Web SDK: - -```bash -yarn add supertokens-web-js -``` - - - - - - - - - - - -Start by installing the SuperTokens web SDK: - -```bash -npm i -s supertokens-web-js -``` - - - - - -Start by installing the SuperTokens web SDK: - -```bash -npm i -s supertokens-web-js -``` - - - - - -Start by installing the SuperTokens web SDK: - -```bash -yarn add supertokens-web-js -``` - - - - - - - - -:::important -SuperTokens does not support pre-built UI for mobile frameworks. Please refer to the [setup guide for custom UI](../../custom-ui/init/frontend) to integrate SuperTokens in your app. -::: - - - - - - - -## 2) Call the `init` function - - - - - - - -```tsx -import React from 'react'; - -// highlight-start -import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; -import Passwordless from "supertokens-auth-react/recipe/passwordless"; -import Session from "supertokens-auth-react/recipe/session"; - -SuperTokens.init({ - appInfo: { - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - Passwordless.init({ - contactMethod: "^{form_contactMethod}" - }), - Session.init() - ] -}); -// highlight-end - - -/* Your App */ -class App extends React.Component { - render() { - return ( - // highlight-next-line - - {/*Your app components*/} - // highlight-next-line - - ); - } -} -``` - - - - - - - - - - - - - - -Before we initialize the `supertokens-web-js` SDK let's see how we will use it in our Angular app - -**Architecture** - -- The `supertokens-web-js` SDK is responsible for session management and providing helper functions to check if a session exists, or validate the access token claims on the frontend (for example, to check for user roles before showing some UI). We will initialise this SDK on the root of your Angular app, so that all pages in your app can use it. -- We will create a `^{form_websiteBasePath}*` route in the Angular app which will render our pre built UI which will also need to be initialised, but only on that route. - -**Creating the `^{form_websiteBasePath}` route** - -- Use the Angular CLI to generate a new route - - ```bash - ng generate module auth --route auth --module app.module - ``` - -- Add the following code to your `auth` angular component - - ```tsx title="/app/auth/auth.component.ts" - import {init as supertokensUIInit} from "supertokens-auth-react-script"; - import supertokensUIPasswordless from "supertokens-auth-react-script/recipe/passwordless"; - import supertokensUISession from "supertokens-auth-react-script/recipe/session"; - 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: [ - supertokensUIPasswordless.init({ - contactMethod: "^{form_contactMethod}" - }), - supertokensUISession.init(), - ], - }); - } - this.renderer.appendChild(this.document.body, script); - } - } - ``` - - In the `loadScript` function, we provide the SuperTokens config for the UI. We add the passwordless and session recipes. - -- Initialize the `supertokens-web-js` SDK in your angular app's root component. This will provide session management across your entire application. - - ```tsx title="/app/app.component.ts " - import SuperTokens from "supertokens-web-js"; - import Session from "supertokens-web-js/recipe/session"; - - SuperTokens.init({ - appInfo: { - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}", - }, - recipeList: [ - Session.init(), - ], - }); - ``` - -
- -
- -
- -
- - - - - - - - - -Before we initialize the `supertokens-web-js` SDK let's see how we will use it in our Vue app - -**Architecture** - -- The `supertokens-web-js` SDK is responsible for session management and providing helper functions to check if a session exists, or validate the access token claims on the frontend (for example, to check for user roles before showing some UI). We will initialise this SDK on the root of your Vue app, so that all pages in your app can use it. -- We will create a `^{form_websiteBasePath}*` route in the Vue app which will render our pre built UI which will also need to be initialised, but only on that route. - -**Creating the `^{form_websiteBasePath}` route** - -- Create a new file `AuthView.vue`, this Vue component will be used to render the auth component: - ```tsx - import {init as supertokensUIInit} from "supertokens-auth-react-script"; - import supertokensUIPasswordless from "supertokens-auth-react-script/recipe/passwordless"; - import supertokensUISession from "supertokens-auth-react-script/recipe/session"; - - - - ``` - - In the `loadScript` function, we provide the SuperTokens config for the UI. We add the passwordless and session recipes. - -- Initialize the `supertokens-web-js` SDK in your Vue app's `main.ts` file. This will provide session management across your entire application. - - ```tsx title="/main.ts " - // @ts-ignore - import { createApp } from "vue"; - import SuperTokens from "supertokens-web-js"; - import Session from "supertokens-web-js/recipe/session"; - // @ts-ignore - import App from "./App.vue"; - // @ts-ignore - import router from "./router"; - - SuperTokens.init({ - appInfo: { - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}", - }, - recipeList: [ - Session.init(), - ], - }); - - const app = createApp(App); - - app.use(router); - - app.mount("#app"); - - ``` - - - - - - - - - - - -:::important -SuperTokens does not support pre-built UI for mobile frameworks. Please refer to the [setup guide for custom UI](../../custom-ui/init/frontend) to integrate SuperTokens in your app. -::: - - - -
- - - - - -## 3) Setup Routing to show the login UI - - - - - - - - - - - -Call the `getSuperTokensRoutesForReactRouterDom` method from within any `react-router-dom` `Routes` component. - -```tsx -import React from 'react'; -import { - BrowserRouter, - Routes, - Route, - Link -} from "react-router-dom"; - -// highlight-next-line -import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; -import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui"; -^{prebuiltuiimport} -import * as reactRouterDom from "react-router-dom"; - -class App extends React.Component { - render() { - return ( - - - - {/*This renders the login UI on the ^{form_websiteBasePath} route*/} - // highlight-next-line - {getSuperTokensRoutesForReactRouterDom(reactRouterDom, [^{recipePreBuiltUINameCapitalLetters}])} - {/*Your app routes*/} - - - - ); - } -} -``` - - - - - -Call the `getSuperTokensRoutesForReactRouterDom` method from within any `react-router-dom` `Switch` component. - -```tsx -import React from 'react'; -import { - BrowserRouter, - Switch, - Route, - Link -} from "react-router-dom5"; - -// highlight-next-line -import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; -import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui"; -^{prebuiltuiimport} -import * as reactRouterDom from "react-router-dom"; - - -class App extends React.Component { - render() { - return ( - - - - {/*This renders the login UI on the ^{form_websiteBasePath} route*/} - // highlight-next-line - {getSuperTokensRoutesForReactRouterDom(reactRouterDom, [^{recipePreBuiltUINameCapitalLetters}])} - {/*Your app routes*/} - - - - ); - } -} -``` - - - - - - - - -Add the highlighted code snippet to your root level `render` function. - -```tsx -import React from 'react'; -^{prebuiltuiimport} -import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; -import { canHandleRoute, getRoutingComponent } from "supertokens-auth-react/ui"; - -class App extends React.Component { - render() { - // highlight-start - if (canHandleRoute([^{recipePreBuiltUINameCapitalLetters}])) { - // This renders the login UI on the ^{form_websiteBasePath} route - return getRoutingComponent([^{recipePreBuiltUINameCapitalLetters}]) - } - // highlight-end - - return ( - {/*Your app*/} - ); - } - -} -``` - - - - - - - - - - -Update your angular router so that all auth related requests load the `auth` component - -```tsx title="/app/app-routing.module.ts" -import { NgModule } from "@angular/core"; -import { RouterModule, Routes } from "@angular/router"; - -const routes: Routes = [ - // highlight-start - { - path: "^{form_websiteBasePath_withoutForwardSlash}", - // @ts-ignore - loadChildren: () => import("./auth/auth.module").then((m) => m.AuthModule), - }, - - // @ts-ignore - { path: "**", loadChildren: () => import("./home/home.module").then((m) => m.HomeModule) }, - // highlight-end -]; - -@NgModule({ - imports: [RouterModule.forRoot(routes)], - exports: [RouterModule], -}) -export class AppRoutingModule {} -``` - - - - - - - - -Update your Vue router so that all auth related requests load the `AuthView` component - -```tsx title="/router/index.ts" -// @ts-ignore -import { createRouter, createWebHistory } from "vue-router"; -// @ts-ignore -import HomeView from "../views/HomeView.vue"; -// @ts-ignore -import AuthView from "../views/AuthView.vue"; - -const router = createRouter({ - // @ts-ignore - history: createWebHistory(import.meta.env.BASE_URL), - routes: [ - { - path: "/", - name: "home", - component: HomeView, - }, - { - path: "^{form_websiteBasePath}/:pathMatch(.*)*", - name: "auth", - component: AuthView, - }, - ], -}); - -export default router; -``` - - - - - - - -:::important -SuperTokens does not support pre-built UI for mobile frameworks. Please refer to the [setup guide for custom UI](../../custom-ui/init/frontend) to integrate SuperTokens in your app. -::: - - - - - - - -## 4) View the login UI - - -^{form_addVisitWebsiteBasePathText} - - - -At this stage, you've successfully integrated your website with SuperTokens. The next section will guide you through setting up your backend. - diff --git a/v2/passwordless/quickstart/backend-setup.mdx b/v2/passwordless/quickstart/backend-setup.mdx new file mode 100644 index 000000000..ede9821f3 --- /dev/null +++ b/v2/passwordless/quickstart/backend-setup.mdx @@ -0,0 +1,1323 @@ +--- +id: backend-setup +title: Backend Setup +hide_title: true +show_ui_switcher: false +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +import PreBuiltUIInjector from "/src/components/prebuiltuiInjector" +import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; +import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" +import {Question, Answer}from "/src/components/question" +import RRDVersionSubTabs from "/src/components/tabs/RRDVersionSubTabs" +import AppInfoForm from "/src/components/appInfoForm" +import NodeJSFrameworkSubTabsServerless from "/src/components/tabs/NodeJSFrameworkSubTabsServerless" +import CoreInjector from "/src/components/coreInjector" +import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs" +import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" +import BackendSDKCasing from "/src/components/BackendSDKCasing" +import CustomAdmonition from "/src/components/customAdmonition" + + +import PasswordlessQuickstartBackendCustomMagicLink from '../../community/reusableMD/passwordless-quickstart-backend-custom-magic-link.mdx' + +import { PasswordlessBackendForm } from "/src/components/snippetConfigForm/passwordlessBackendForm"; +import { ConditionalSection } from "/src/components/snippetConfigForm"; + +# Backend Setup + +Let's got through the changes required so that your backend can expose the **SuperTokens** authentication features. + +## 1. Install the SDK + + + + + +Run the following command in your terminal to install the package. + + + + +```bash +npm i -s supertokens-node +``` + + + + +```bash +go get github.com/supertokens/supertokens-golang +``` + + + + +```bash +pip install supertokens-python +``` + + + + + + +## 2. Initialize the SDK + +You will have to intialize the **Backend SDK** alongside the code that starts your server. +The init call will include [configuration details](../appinfo) for your app, how the backend will connect to the **SuperTokens Core**, as well as the **Recipes** that will be used in your setup. + +For the **Passwordless** recipe, you will also need to specify the `flowType` and `contactMethod`. +Just click one of the options from the next form and the code snippet will get updated. + + + + + + +Add the code below to your server's init file. + + + + + + + +```tsx +import supertokens from "supertokens-node"; +import Session from "supertokens-node/recipe/session"; +import Passwordless from "supertokens-node/recipe/passwordless"; + +supertokens.init({ + framework: "express", + supertokens: { + ^{coreInjector_connection_uri_comment} + connectionURI: ^{coreInjector_uri} + ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, + }, + appInfo: { + // learn more about this on https://supertokens.com/docs/session/appinfo + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + websiteDomain: "^{form_websiteDomain}", + apiBasePath: "^{form_apiBasePath}", + websiteBasePath: "^{form_websiteBasePath}" + }, + recipeList: [ + Passwordless.init({ + flowType: "^{form_flowType}", + contactMethod: "^{form_contactMethod}" + }), + Session.init() // initializes session features + ] +}); +``` + + + + + +```tsx +import supertokens from "supertokens-node"; +import Session from "supertokens-node/recipe/session"; +import Passwordless from "supertokens-node/recipe/passwordless"; + +supertokens.init({ + framework: "hapi", + supertokens: { + ^{coreInjector_connection_uri_comment} + connectionURI: ^{coreInjector_uri} + ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, + }, + appInfo: { + // learn more about this on https://supertokens.com/docs/session/appinfo + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + websiteDomain: "^{form_websiteDomain}", + apiBasePath: "^{form_apiBasePath}", + websiteBasePath: "^{form_websiteBasePath}" + }, + recipeList: [ + Passwordless.init({ + flowType: "^{form_flowType}", + contactMethod: "^{form_contactMethod}" + }), + Session.init() // initializes session features + ] +}); +``` + + + + +```tsx +import supertokens from "supertokens-node"; +import Session from "supertokens-node/recipe/session"; +import Passwordless from "supertokens-node/recipe/passwordless"; + +supertokens.init({ + framework: "fastify", + supertokens: { + ^{coreInjector_connection_uri_comment} + connectionURI: ^{coreInjector_uri} + ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, + }, + appInfo: { + // learn more about this on https://supertokens.com/docs/session/appinfo + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + websiteDomain: "^{form_websiteDomain}", + apiBasePath: "^{form_apiBasePath}", + websiteBasePath: "^{form_websiteBasePath}" + }, + recipeList: [ + Passwordless.init({ + flowType: "^{form_flowType}", + contactMethod: "^{form_contactMethod}" + }), + Session.init() // initializes session features + ] +}); +``` + + + + +```tsx +import supertokens from "supertokens-node"; +import Session from "supertokens-node/recipe/session"; +import Passwordless from "supertokens-node/recipe/passwordless"; + +supertokens.init({ + framework: "koa", + supertokens: { + ^{coreInjector_connection_uri_comment} + connectionURI: ^{coreInjector_uri} + ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, + }, + appInfo: { + // learn more about this on https://supertokens.com/docs/session/appinfo + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + websiteDomain: "^{form_websiteDomain}", + apiBasePath: "^{form_apiBasePath}", + websiteBasePath: "^{form_websiteBasePath}" + }, + recipeList: [ + Passwordless.init({ + flowType: "^{form_flowType}", + contactMethod: "^{form_contactMethod}" + }), + Session.init() // initializes session features + ] +}); +``` + + + + +```tsx +import supertokens from "supertokens-node"; +import Session from "supertokens-node/recipe/session"; +import Passwordless from "supertokens-node/recipe/passwordless"; + +supertokens.init({ + framework: "loopback", + supertokens: { + ^{coreInjector_connection_uri_comment} + connectionURI: ^{coreInjector_uri} + ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, + }, + appInfo: { + // learn more about this on https://supertokens.com/docs/session/appinfo + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + websiteDomain: "^{form_websiteDomain}", + apiBasePath: "^{form_apiBasePath}", + websiteBasePath: "^{form_websiteBasePath}" + }, + recipeList: [ + Passwordless.init({ + flowType: "^{form_flowType}", + contactMethod: "^{form_contactMethod}" + }), + Session.init() // initializes session features + ] +}); +``` + + + + + +```go +import ( + "github.com/supertokens/supertokens-golang/recipe/session" + "github.com/supertokens/supertokens-golang/recipe/passwordless" + "github.com/supertokens/supertokens-golang/recipe/passwordless/plessmodels" + "github.com/supertokens/supertokens-golang/supertokens" +) + +func main() { + apiBasePath := "^{form_apiBasePath}" + websiteBasePath := "^{form_websiteBasePath}" + err := supertokens.Init(supertokens.TypeInput{ + Supertokens: &supertokens.ConnectionInfo{ + ^{coreInjector_connection_uri_comment} + ConnectionURI: ^{coreInjector_uri} + ^{coreInjector_api_key_commented}APIKey: ^{coreInjector_api_key}, + }, + AppInfo: supertokens.AppInfo{ + AppName: "^{form_appName}", + APIDomain: "^{form_apiDomain}", + WebsiteDomain: "^{form_websiteDomain}", + APIBasePath: &apiBasePath, + WebsiteBasePath: &websiteBasePath, + }, + RecipeList: []supertokens.Recipe{ + passwordless.Init(plessmodels.TypeInput{ + FlowType: "^{form_flowType}", + ^{form_contactMethod_sendCB_Go} + }), + session.Init(nil), // initializes session features + }, + }) + + if err != nil { + panic(err.Error()) + } +} +``` + + + + + + +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import passwordless, session + +^{form_contactMethod_import_Python} +init( + app_info=InputAppInfo( + app_name="^{form_appName}", + api_domain="^{form_apiDomain}", + website_domain="^{form_websiteDomain}", + api_base_path="^{form_apiBasePath}", + website_base_path="^{form_websiteBasePath}" + ), + supertokens_config=SupertokensConfig( + ^{coreInjector_connection_uri_comment_with_hash} + connection_uri=^{coreInjector_uri} + ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} + ), + framework='fastapi', + recipe_list=[ + session.init(), # initializes session features + passwordless.init( + flow_type="^{form_flowType}", + contact_config=^{form_contactMethod_initialize_Python}() + ) + ], + mode='asgi' # use wsgi if you are running using gunicorn +) +``` + + + + +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import passwordless, session + +^{form_contactMethod_import_Python} +init( + app_info=InputAppInfo( + app_name="^{form_appName}", + api_domain="^{form_apiDomain}", + website_domain="^{form_websiteDomain}", + api_base_path="^{form_apiBasePath}", + website_base_path="^{form_websiteBasePath}" + ), + supertokens_config=SupertokensConfig( + ^{coreInjector_connection_uri_comment_with_hash} + connection_uri=^{coreInjector_uri} + ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} + ), + framework='flask', + recipe_list=[ + session.init(), # initializes session features + passwordless.init( + flow_type="^{form_flowType}", + contact_config=^{form_contactMethod_initialize_Python}() + ) + ] +) +``` + + + + +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import passwordless, session + +^{form_contactMethod_import_Python} +init( + app_info=InputAppInfo( + app_name="^{form_appName}", + api_domain="^{form_apiDomain}", + website_domain="^{form_websiteDomain}", + api_base_path="^{form_apiBasePath}", + website_base_path="^{form_websiteBasePath}" + ), + supertokens_config=SupertokensConfig( + ^{coreInjector_connection_uri_comment_with_hash} + connection_uri=^{coreInjector_uri} + ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} + ), + framework='django', + recipe_list=[ + session.init(), # initializes session features + passwordless.init( + flow_type="^{form_flowType}", + contact_config=^{form_contactMethod_initialize_Python}() + ) + ], + mode='asgi' # use wsgi if you are running django server in sync mode +) +``` + + + + + + + + + + + + + + + + + + + + + + + + + + +## 3. Add the SuperTokens APIs and Configure CORS + + + + + +Now that the SDK is initialized you need to expose the endpoints that will be used by the frontend SDKs. +Besides this, your server's CORS, Cross-Origin Resource Sharing, settings should be updated to allow the use of the authentication headers required by **SuperTokens**. + + + + + + + + +:::important +- Add the `middleware` BEFORE all your routes. +- Add the `cors` middleware BEFORE the SuperTokens middleware as shown below. +::: + +```tsx +import express from "express"; +import cors from "cors"; +import supertokens from "supertokens-node"; +import { middleware } from "supertokens-node/framework/express"; + +let app = express(); + +app.use(cors({ + // highlight-start + origin: "^{form_websiteDomain}", + allowedHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], + credentials: true, + // highlight-end +})); + +// IMPORTANT: CORS should be before the below line. +// highlight-next-line +app.use(middleware()); + +// ...your API routes +``` + + + + +Register the `plugin`. + +```tsx +import Hapi from "@hapi/hapi"; +import supertokens from "supertokens-node"; +import { plugin } from "supertokens-node/framework/hapi"; + +let server = Hapi.server({ + port: 8000, + routes: { + // highlight-start + cors: { + origin: ["^{form_websiteDomain}"], + additionalHeaders: [...supertokens.getAllCORSHeaders()], + credentials: true, + } + // highlight-end + } +}); + +(async () => { + // highlight-next-line + await server.register(plugin); + + await server.start(); +})(); + +// ...your API routes +``` + + + + +Register the `plugin`. Also register [`@fastify/formbody`](https://github.com/fastify/fastify-formbody) plugin. + +```tsx +import cors from "@fastify/cors"; +import supertokens from "supertokens-node"; +import { plugin } from "supertokens-node/framework/fastify"; +import formDataPlugin from "@fastify/formbody"; + +import fastifyImport from "fastify"; + +let fastify = fastifyImport(); + +// ...other middlewares +// highlight-start +fastify.register(cors, { + origin: "^{form_websiteDomain}", + allowedHeaders: ['Content-Type', ...supertokens.getAllCORSHeaders()], + credentials: true, +}); +// highlight-end + +(async () => { + // highlight-next-line + await fastify.register(formDataPlugin); + // highlight-next-line + await fastify.register(plugin); + + await fastify.listen(8000); +})(); + +// ...your API routes +``` + + + + +:::important +Add the `middleware` BEFORE all your routes. +::: + +```tsx +import Koa from "koa"; +import cors from '@koa/cors'; +import supertokens from "supertokens-node"; +import { middleware } from "supertokens-node/framework/koa"; + +let app = new Koa(); + +app.use(cors({ + // highlight-start + origin: "^{form_websiteDomain}", + allowHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], + credentials: true, + // highlight-end +})); + +// highlight-next-line +app.use(middleware()); + +// ...your API routes +``` + + + + +:::important +Add the `middleware` BEFORE all your routes. +::: + +```tsx +import { RestApplication } from "@loopback/rest"; +import supertokens from "supertokens-node"; +import { middleware } from "supertokens-node/framework/loopback"; + +let app = new RestApplication({ + rest: { + cors: { + // highlight-start + origin: "^{form_websiteDomain}", + allowedHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], + credentials: true + // highlight-end + } + } +}); + +// highlight-next-line +app.middleware(middleware); + +// ...your API routes +``` + + + + + + + + +Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. + +```go +import ( + "net/http" + "strings" + + "github.com/supertokens/supertokens-golang/supertokens" +) + +func main() { + // SuperTokens init... + + //highlight-start + http.ListenAndServe("SERVER ADDRESS", corsMiddleware( + supertokens.Middleware(http.HandlerFunc(func(rw http.ResponseWriter, + //highlight-end + r *http.Request) { + // TODO: Handle your APIs.. + + })))) +} + +func corsMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(response http.ResponseWriter, r *http.Request) { + response.Header().Set("Access-Control-Allow-Origin", "^{form_websiteDomain}") + response.Header().Set("Access-Control-Allow-Credentials", "true") + if r.Method == "OPTIONS" { + // we add content-type + other headers used by SuperTokens + response.Header().Set("Access-Control-Allow-Headers", + strings.Join(append([]string{"Content-Type"}, + //highlight-start + supertokens.GetAllCORSHeaders()...), ",")) + //highlight-end + response.Header().Set("Access-Control-Allow-Methods", "*") + response.Write([]byte("")) + } else { + next.ServeHTTP(response, r) + } + }) +} +``` + + + + +Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. + +```go +import ( + "net/http" + + "github.com/gin-contrib/cors" + "github.com/gin-gonic/gin" + "github.com/supertokens/supertokens-golang/supertokens" +) + +func main() { + // SuperTokens init... + + router := gin.New() + + // CORS + router.Use(cors.New(cors.Config{ + AllowOrigins: []string{"^{form_websiteDomain}"}, + AllowMethods: []string{"GET", "POST", "DELETE", "PUT", "OPTIONS"}, + AllowHeaders: append([]string{"content-type"}, + // highlight-next-line + supertokens.GetAllCORSHeaders()...), + AllowCredentials: true, + })) + + // Adding the SuperTokens middleware + // highlight-start + router.Use(func(c *gin.Context) { + supertokens.Middleware(http.HandlerFunc( + func(rw http.ResponseWriter, r *http.Request) { + c.Next() + })).ServeHTTP(c.Writer, c.Request) + // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly + c.Abort() + }) + // highlight-end + + // Add APIs and start server +} +``` + + + + +Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. + +```go +import ( + "github.com/go-chi/chi" + "github.com/go-chi/cors" + "github.com/supertokens/supertokens-golang/supertokens" +) + +func main() { + // SuperTokens init... + + r := chi.NewRouter() + + // CORS + r.Use(cors.Handler(cors.Options{ + AllowedOrigins: []string{"^{form_websiteDomain}"}, + AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, + AllowedHeaders: append([]string{"Content-Type"}, + //highlight-next-line + supertokens.GetAllCORSHeaders()...), + AllowCredentials: true, + })) + + // SuperTokens Middleware + //highlight-next-line + r.Use(supertokens.Middleware) + + // Add APIs and start server +} +``` + + + +Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. + +```go +import ( + "net/http" + + "github.com/gorilla/handlers" + "github.com/gorilla/mux" + "github.com/supertokens/supertokens-golang/supertokens" +) + +func main() { + // SuperTokens init... + + // TODO: Add APIs + + router := mux.NewRouter() + + // Adding handlers.CORS(options)(supertokens.Middleware(router))) + //highlight-start + http.ListenAndServe("SERVER ADDRESS", handlers.CORS( + handlers.AllowedHeaders(append([]string{"Content-Type"}, + supertokens.GetAllCORSHeaders()...)), + handlers.AllowedMethods([]string{"GET", "POST", "PUT", "HEAD", "OPTIONS"}), + handlers.AllowedOrigins([]string{"^{form_websiteDomain}"}), + handlers.AllowCredentials(), + )(supertokens.Middleware(router))) + //highlight-end +} +``` + + + + + + + + + +Use the `Middleware` (**BEFORE all your routes**) and the `get_all_cors_headers()` functions as shown below. + +```python +from supertokens_python import get_all_cors_headers +from fastapi import FastAPI +from starlette.middleware.cors import CORSMiddleware +from supertokens_python.framework.fastapi import get_middleware + +app = FastAPI() +# highlight-next-line +app.add_middleware(get_middleware()) + +# TODO: Add APIs + +app.add_middleware( + CORSMiddleware, + allow_origins=[ + "^{form_websiteDomain}" + ], + allow_credentials=True, + allow_methods=["GET", "PUT", "POST", "DELETE", "OPTIONS", "PATCH"], + # highlight-next-line + allow_headers=["Content-Type"] + get_all_cors_headers(), +) + +# TODO: start server +``` + + + + +- Use the `Middleware` (**BEFORE all your routes and after calling init function**) and the `get_all_cors_headers()` functions as shown below. +- Add a route to catch all paths and return a 404. This is needed because if we don't add this, then OPTIONS request for the APIs exposed by the `Middleware` will return a `404`. + +```python +from supertokens_python import get_all_cors_headers +from flask import Flask, abort +from flask_cors import CORS # type: ignore +from supertokens_python.framework.flask import Middleware + +app = Flask(__name__) +# highlight-next-line +Middleware(app) + +# TODO: Add APIs + +CORS( + app=app, + origins=[ + "^{form_websiteDomain}" + ], + supports_credentials=True, + # highlight-next-line + allow_headers=["Content-Type"] + get_all_cors_headers(), +) + +# This is required since if this is not there, then OPTIONS requests for +# the APIs exposed by the supertokens' Middleware will return a 404 +# highlight-start +@app.route('/', defaults={'u_path': ''}) # type: ignore +@app.route('/') # type: ignore +def catch_all(u_path: str): + abort(404) +# highlight-end + +# TODO: start server +``` + + + + +Use the `Middleware` and the `get_all_cors_headers()` functions as shown below in your `settings.py`. + +```python +from supertokens_python import get_all_cors_headers +from typing import List +from corsheaders.defaults import default_headers + +CORS_ORIGIN_WHITELIST = [ + "^{form_websiteDomain}" +] + +CORS_ALLOW_CREDENTIALS = True + +CORS_ALLOWED_ORIGINS = [ + "^{form_websiteDomain}" +] + +CORS_ALLOW_HEADERS: List[str] = list(default_headers) + [ + "Content-Type" + # highlight-next-line +] + get_all_cors_headers() + +INSTALLED_APPS = [ + 'corsheaders', + 'supertokens_python' +] + +MIDDLEWARE = [ + 'corsheaders.middleware.CorsMiddleware', + ..., + # highlight-next-line + 'supertokens_python.framework.django.django_middleware.middleware', +] +# TODO: start server +``` + + + + + + + + + + + +You can review all the endpoints that are added through the use of **SuperTokens** by visiting the [API Specs](https://app.swaggerhub.com/apis/supertokens/FDI). + + + + + +## 4. Add the SuperTokens Error Handler + + + + + + + +Depending on the language and framework that you are using, you might need to add a custom error handler to your server. +The handler will catch all the authentication related errors and return proper HTTP responses that can be parsed by the frontend SDKs. + + + + + + +```tsx +import express, { Request, Response, NextFunction } from 'express'; +import { errorHandler } from "supertokens-node/framework/express"; + +let app = express(); + +// ...your API routes + +// highlight-start +// Add this AFTER all your routes +app.use(errorHandler()) +// highlight-end + +// your own error handler +app.use((err: unknown, req: Request, res: Response, next: NextFunction) => { /* ... */ }); +``` + + + +No additional `errorHandler` is required. + + + + +Add the `errorHandler` **Before all your routes and plugin registration** + +```tsx +import Fastify from "fastify"; +import { errorHandler } from "supertokens-node/framework/fastify"; + +let fastify = Fastify(); + +// highlight-next-line +fastify.setErrorHandler(errorHandler()); + +// ...your API routes +``` + + + +No additional `errorHandler` is required. + + + + +No additional `errorHandler` is required. + + + + + + +:::info +You can skip this step +::: + + + + +:::info +You can skip this step +::: + + + + + + +## 5. Secure Application Routes + +Now that your server can authenticate users, the final step that you need to take care of is to prevent unauthorized access to certain parts of the application. + + + + +For your APIs that require a user to be logged in, use the `verifySession` middleware. + + + + + +```tsx +import express from "express"; +import { verifySession } from "supertokens-node/recipe/session/framework/express"; +import { SessionRequest } from "supertokens-node/framework/express"; + +let app = express(); + +// highlight-start +app.post("/like-comment", verifySession(), (req: SessionRequest, res) => { + let userId = req.session!.getUserId(); + // highlight-end + //.... +}); +``` + + + + +```tsx +import Hapi from "@hapi/hapi"; +import { verifySession } from "supertokens-node/recipe/session/framework/hapi"; +import { SessionRequest } from "supertokens-node/framework/hapi"; + +let server = Hapi.server({ port: 8000 }); + +server.route({ + path: "/like-comment", + method: "post", + //highlight-start + options: { + pre: [ + { + method: verifySession() + }, + ], + }, + handler: async (req: SessionRequest, res) => { + let userId = req.session!.getUserId(); + //highlight-end + //... + } +}) +``` + + + +```tsx +import Fastify from "fastify"; +import { verifySession } from "supertokens-node/recipe/session/framework/fastify"; +import { SessionRequest } from "supertokens-node/framework/fastify"; + +let fastify = Fastify(); + +//highlight-start +fastify.post("/like-comment", { + preHandler: verifySession(), +}, (req: SessionRequest, res) => { + let userId = req.session!.getUserId(); + //highlight-end + //.... +}); +``` + + + + +```tsx +import KoaRouter from "koa-router"; +import { verifySession } from "supertokens-node/recipe/session/framework/koa"; +import { SessionContext } from "supertokens-node/framework/koa"; + +let router = new KoaRouter(); + +//highlight-start +router.post("/like-comment", verifySession(), (ctx: SessionContext, next) => { + let userId = ctx.session!.getUserId(); + //highlight-end + //.... +}); +``` + + + + +```tsx +import { inject, intercept } from "@loopback/core"; +import { RestBindings, MiddlewareContext, post, response } from "@loopback/rest"; +import { verifySession } from "supertokens-node/recipe/session/framework/loopback"; +import { SessionContext } from "supertokens-node/framework/loopback"; + +class LikeComment { + //highlight-start + constructor(@inject(RestBindings.Http.CONTEXT) private ctx: MiddlewareContext) { } + @post("/like-comment") + @intercept(verifySession()) + @response(200) + handler() { + let userId = (this.ctx as SessionContext).session!.getUserId(); + //highlight-end + //.... + } +} +``` + + + + + + +For your APIs that require a user to be logged in, use the `VerifySession` middleware. + + + + +```go +import ( + "fmt" + "net/http" + + "github.com/supertokens/supertokens-golang/recipe/session" +) + +func main() { + _ = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + // Wrap the API handler in session.VerifySession + session.VerifySession(nil, likeCommentAPI).ServeHTTP(rw, r) + }) +} + +func likeCommentAPI(w http.ResponseWriter, r *http.Request) { + // retrieve the session object as shown below + sessionContainer := session.GetSessionFromRequestContext(r.Context()) + + userID := sessionContainer.GetUserID() + + fmt.Println(userID) +} +``` + + + + +```go +import ( + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/supertokens/supertokens-golang/recipe/session" + "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" +) + +func main() { + router := gin.New() + + // Wrap the API handler in session.VerifySession + router.POST("/likecomment", verifySession(nil), likeCommentAPI) +} + +// This is a function that wraps the supertokens verification function +// to work the gin +func verifySession(options *sessmodels.VerifySessionOptions) gin.HandlerFunc { + return func(c *gin.Context) { + session.VerifySession(options, func(rw http.ResponseWriter, r *http.Request) { + c.Request = c.Request.WithContext(r.Context()) + c.Next() + })(c.Writer, c.Request) + // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly + c.Abort() + } +} + +func likeCommentAPI(c *gin.Context) { + // retrieve the session object as shown below + sessionContainer := session.GetSessionFromRequestContext(c.Request.Context()) + + userID := sessionContainer.GetUserID() + + fmt.Println(userID) +} +``` + + + + +```go +import ( + "fmt" + "net/http" + + "github.com/go-chi/chi" + "github.com/supertokens/supertokens-golang/recipe/session" +) + +func main() { + r := chi.NewRouter() + + // Wrap the API handler in session.VerifySession + r.Post("/likecomment", session.VerifySession(nil, likeCommentAPI)) +} + +func likeCommentAPI(w http.ResponseWriter, r *http.Request) { + // retrieve the session object as shown below + sessionContainer := session.GetSessionFromRequestContext(r.Context()) + + userID := sessionContainer.GetUserID() + + fmt.Println(userID) +} +``` + + + + +```go +import ( + "fmt" + "net/http" + + "github.com/gorilla/mux" + "github.com/supertokens/supertokens-golang/recipe/session" +) + +func main() { + router := mux.NewRouter() + + // Wrap the API handler in session.VerifySession + router.HandleFunc("/likecomment", session.VerifySession(nil, likeCommentAPI)).Methods(http.MethodPost) +} + +func likeCommentAPI(w http.ResponseWriter, r *http.Request) { + // retrieve the session object as shown below + sessionContainer := session.GetSessionFromRequestContext(r.Context()) + + userID := sessionContainer.GetUserID() + + fmt.Println(userID) +} +``` + + + + + + +For your APIs that require a user to be logged in, use the `verify_session` middleware. + + + + +```python +from supertokens_python.recipe.session.framework.fastapi import verify_session +from supertokens_python.recipe.session import SessionContainer +from fastapi import Depends + +# highlight-start +@app.post('/like_comment') # type: ignore +async def like_comment(session: SessionContainer = Depends(verify_session())): + user_id = session.get_user_id() + # highlight-end + + print(user_id) +``` + + + + +```python +from supertokens_python.recipe.session.framework.flask import verify_session +from supertokens_python.recipe.session import SessionContainer +from flask import g + +# highlight-start +@app.route('/update-jwt', methods=['POST']) # type: ignore +@verify_session() +def like_comment(): + session: SessionContainer = g.supertokens # type: ignore + + user_id = session.get_user_id() + # highlight-end + + print(user_id) +``` + + + + +```python +from supertokens_python.recipe.session.framework.django.asyncio import verify_session +from django.http import HttpRequest +from supertokens_python.recipe.session import SessionContainer + +# highlight-start +@verify_session() +async def like_comment(request: HttpRequest): + session: SessionContainer = request.supertokens # type: ignore + + user_id = session.get_user_id() + # highlight-end + + print(user_id) +``` + + + + + + + +The middleware function returns a `401` to the frontend if a session doesn't exist, or if the access token has expired, in which case, our frontend SDK automatically refreshes the session. + +In case of successful session verification, you get access to a `session` object using which you can get the user's ID, or manipulate the session information. + +## 6. Test the Login Flow + +Now that you have configured both the frontend and the backend, you can return to the frontend login page. +From here follow these steps to confirm that your setup is working properly. +- Click on the **Sign up** button to create a new account. +- After you have created the account go to **Login** page and fill in your credentials. +- If you are greeted with the login screen you have completed the quickstart setup. + +:::success 🎉 Congratulations 🎉 + +You've successfully integrated **SuperTokens** with your existing application! + +Of course, there are additional things that you should add in order to provide a complete authentication experience. +We will talk about those things in the [next section](./next-steps). + +::: diff --git a/v2/passwordless/quickstart/frontend-setup.mdx b/v2/passwordless/quickstart/frontend-setup.mdx new file mode 100644 index 000000000..a34917c09 --- /dev/null +++ b/v2/passwordless/quickstart/frontend-setup.mdx @@ -0,0 +1,904 @@ +--- +id: frontend-setup +title: Frontend Setup +hide_title: true +toc_max_heading_level: 4 +show_ui_switcher: true +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" +import WebJsInjector from "/src/components/webJsInjector" + +import FrontendCustomUISDKInstall from '../../community/reusableMD/custom-ui/frontent-custom-ui-sdk-install.mdx' +import FrontendCustomUISessionTokens from '../../community/reusableMD/custom-ui/frontend-custom-ui-session-tokens.mdx' +import FrontendCustomUISessionManagement from '../../community/reusableMD/custom-ui/frontend-custom-ui-session-management.mdx' +import FrontendCustomUISignout from '../../community/reusableMD/custom-ui/frontend-custom-ui-signout.mdx' +import FrontendCustomUIPasswordless from '../../community/reusableMD/custom-ui/frontend-custom-ui-passwordless.mdx' +import FrontendSDKInstall from "../../community/reusableMD/frontend-sdk-install.mdx" + + +import {CustomUILink, PrebuiltUILink, PreBuiltOrCustomUISwitcher, PreBuiltUIContent, CustomUIContent} from "/src/components/preBuiltOrCustomUISwitcher" +import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" +import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" +import PreBuiltUIInjector from "/src/components/prebuiltuiInjector" +import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; +import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" +import {Question, Answer}from "/src/components/question" +import RRDVersionSubTabs from "/src/components/tabs/RRDVersionSubTabs" +import AppInfoForm from "/src/components/appInfoForm" +import NodeJSFrameworkSubTabsServerless from "/src/components/tabs/NodeJSFrameworkSubTabsServerless" +import NpmVersionOrYarnSubTabs from "/src/components/tabs/NpmVersionOrYarnSubTabs" +import CoreInjector from "/src/components/coreInjector" +import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs" +import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" +import BackendSDKCasing from "/src/components/BackendSDKCasing" + +import { PasswordlessFrontendForm } from "/src/components/snippetConfigForm/passwordlessFrontendForm"; + + +# Frontend Setup + +Start the setup by configuring your frontend application to use **SuperTokens** for authentication. + + + + + +This guide uses the **SuperTokens Pre Built UI** components. +If you want to create your own interface please check the **Custom UI** tutorial. + +## 1. Install the SDK + + + + + +Run the following command in your terminal to install the package. + + + + + +## 2. Initialize the SDK + + + + + + + + + + + +In your main application file call the `SuperTokens.init` function to initialize the SDK. +The `init` call includes the [main configuration details](../appinfo), as well as the **recipes** that you will be using in your setup. +After that you will have to wrap the application with the `SuperTokensWrapper` component. +This will provide authentication context for the rest of the UI tree. + + + +You also have to specify which `contact method` should be used for the `passwordless` flow. +Just click one of the options from the next form and the code snippet will get updated. + + + +```tsx +import React from 'react'; + +// highlight-start +import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; +import Passwordless from "supertokens-auth-react/recipe/passwordless"; +import Session from "supertokens-auth-react/recipe/session"; + +SuperTokens.init({ + appInfo: { + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + websiteDomain: "^{form_websiteDomain}", + apiBasePath: "^{form_apiBasePath}", + websiteBasePath: "^{form_websiteBasePath}" + }, + recipeList: [ + Passwordless.init({ + contactMethod: "^{form_contactMethod}" + }), + Session.init() + ] +}); +// highlight-end + + +/* Your App */ +class App extends React.Component { + render() { + return ( + // highlight-next-line + + {/*Your app components*/} + // highlight-next-line + + ); + } +} +``` + + + + + + + + + + + + +Specify which `contact method` should be used for the `passwordless` flow. +Just click one of the options from the next form and the code snippet will get updated. + + + +Before we initialize the `supertokens-web-js` SDK let's see how we will use it in our Angular app + +**Architecture** + +- The `supertokens-web-js` SDK is responsible for session management and providing helper functions to check if a session exists, or validate the access token claims on the frontend (for example, to check for user roles before showing some UI). We will initialise this SDK on the root of your Angular app, so that all pages in your app can use it. +- We will create a `^{form_websiteBasePath}*` route in the Angular app which will render our pre built UI which will also need to be initialised, but only on that route. + +**Creating the `^{form_websiteBasePath}` route** + +- Use the Angular CLI to generate a new route + + ```bash + ng generate module auth --route auth --module app.module + ``` + +- Add the following code to your `auth` angular component + + ```tsx title="/app/auth/auth.component.ts" + import {init as supertokensUIInit} from "supertokens-auth-react-script"; + import supertokensUIPasswordless from "supertokens-auth-react-script/recipe/passwordless"; + import supertokensUISession from "supertokens-auth-react-script/recipe/session"; + 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: [ + supertokensUIPasswordless.init({ + contactMethod: "^{form_contactMethod}" + }), + supertokensUISession.init(), + ], + }); + } + this.renderer.appendChild(this.document.body, script); + } + } + ``` + - In the `loadScript` function, we provide the SuperTokens config for the UI. We add the passwordless and session recipes. + +- Initialize the `supertokens-web-js` SDK in your angular app's root component. This will provide session management across your entire application. + + ```tsx title="/app/app.component.ts " + import SuperTokens from "supertokens-web-js"; + import Session from "supertokens-web-js/recipe/session"; + + SuperTokens.init({ + appInfo: { + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}", + }, + recipeList: [ + Session.init(), + ], + }); + ``` + +
+ +
+ +
+ +
+ + + + + + + +Specify which `contact method` should be used for the `passwordless` flow. +Just click one of the options from the next form and the code snippet will get updated. + + + +Before we initialize the `supertokens-web-js` SDK let's see how we will use it in our Vue app + +**Architecture** + +- The `supertokens-web-js` SDK is responsible for session management and providing helper functions to check if a session exists, or validate the access token claims on the frontend (for example, to check for user roles before showing some UI). We will initialise this SDK on the root of your Vue app, so that all pages in your app can use it. +- We will create a `^{form_websiteBasePath}*` route in the Vue app which will render our pre built UI which will also need to be initialised, but only on that route. + +**Creating the `^{form_websiteBasePath}` route** + +- Create a new file `AuthView.vue`, this Vue component will be used to render the auth component: + ```tsx + import {init as supertokensUIInit} from "supertokens-auth-react-script"; + import supertokensUIPasswordless from "supertokens-auth-react-script/recipe/passwordless"; + import supertokensUISession from "supertokens-auth-react-script/recipe/session"; + + + + ``` + - In the `loadScript` function, we provide the SuperTokens config for the UI. We add the passwordless and session recipes. + +- Initialize the `supertokens-web-js` SDK in your Vue app's `main.ts` file. This will provide session management across your entire application. + + ```tsx title="/main.ts " + // @ts-ignore + import { createApp } from "vue"; + import SuperTokens from "supertokens-web-js"; + import Session from "supertokens-web-js/recipe/session"; + // @ts-ignore + import App from "./App.vue"; + // @ts-ignore + import router from "./router"; + + SuperTokens.init({ + appInfo: { + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}", + }, + recipeList: [ + Session.init(), + ], + }); + + const app = createApp(App); + + app.use(router); + + app.mount("#app"); + + ``` + + + + + + + + + + + +:::important +SuperTokens does not support pre-built UI for mobile frameworks. Please toggle the **Custom UI** option from the *Table of Contents* section. +::: + + + +
+ +## 3. Configure Routing + + + + + + + + +In order for the **Pre Built UI** to be rendered inside your application, will will have to specify which routes will show the authentication components. +The **React SDK** uses [**React Router**](https://reactrouter.com/en/main) under the hood to achieve this. +Based on whether you already use this package or not in your project, there are two different ways of configuring the routes. + + + + + + + + + +Call the `getSuperTokensRoutesForReactRouterDom` method from within any `react-router-dom` `Routes` component. + +```tsx +import React from 'react'; +import { + BrowserRouter, + Routes, + Route, + Link +} from "react-router-dom"; + +// highlight-next-line +import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; +import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui"; +^{prebuiltuiimport} +import * as reactRouterDom from "react-router-dom"; + +class App extends React.Component { + render() { + return ( + + + + {/*This renders the login UI on the ^{form_websiteBasePath} route*/} + // highlight-next-line + {getSuperTokensRoutesForReactRouterDom(reactRouterDom, [^{recipePreBuiltUINameCapitalLetters}])} + {/*Your app routes*/} + + + + ); + } +} +``` + + + + + +Call the `getSuperTokensRoutesForReactRouterDom` method from within any `react-router-dom` `Switch` component. + +```tsx +import React from 'react'; +import { + BrowserRouter, + Switch, + Route, + Link +} from "react-router-dom5"; + +// highlight-next-line +import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; +import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui"; +^{prebuiltuiimport} +import * as reactRouterDom from "react-router-dom"; + + +class App extends React.Component { + render() { + return ( + + + + {/*This renders the login UI on the ^{form_websiteBasePath} route*/} + // highlight-next-line + {getSuperTokensRoutesForReactRouterDom(reactRouterDom, [^{recipePreBuiltUINameCapitalLetters}])} + {/*Your app routes*/} + + + + ); + } +} +``` + + + + + + + + +Add the highlighted code snippet to your root level `render` function. + +```tsx +import React from 'react'; +^{prebuiltuiimport} +import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; +import { canHandleRoute, getRoutingComponent } from "supertokens-auth-react/ui"; + +class App extends React.Component { + render() { + // highlight-start + if (canHandleRoute([^{recipePreBuiltUINameCapitalLetters}])) { + // This renders the login UI on the ^{form_websiteBasePath} route + return getRoutingComponent([^{recipePreBuiltUINameCapitalLetters}]) + } + // highlight-end + + return ( + {/*Your app*/} + ); + } + +} +``` + + + + + + + + + + +Update your angular router so that all auth related requests load the `auth` component + +```tsx title="/app/app-routing.module.ts" +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +const routes: Routes = [ + // highlight-start + { + path: "^{form_websiteBasePath_withoutForwardSlash}", + // @ts-ignore + loadChildren: () => import("./auth/auth.module").then((m) => m.AuthModule), + }, + + // @ts-ignore + { path: "**", loadChildren: () => import("./home/home.module").then((m) => m.HomeModule) }, + // highlight-end +]; + +@NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule], +}) +export class AppRoutingModule {} +``` + + + + + + + + +Update your Vue router so that all auth related requests load the `AuthView` component + +```tsx title="/router/index.ts" +// @ts-ignore +import { createRouter, createWebHistory } from "vue-router"; +// @ts-ignore +import HomeView from "../views/HomeView.vue"; +// @ts-ignore +import AuthView from "../views/AuthView.vue"; + +const router = createRouter({ + // @ts-ignore + history: createWebHistory(import.meta.env.BASE_URL), + routes: [ + { + path: "/", + name: "home", + component: HomeView, + }, + { + path: "^{form_websiteBasePath}/:pathMatch(.*)*", + name: "auth", + component: AuthView, + }, + ], +}); + +export default router; +``` + + + + + + + +:::important +SuperTokens does not support pre-built UI for mobile frameworks. Please toggle the **Custom UI** option from the *Table of Contents* section. +::: + + + + + + + +## 4. Handle Session Tokens + + + + + +This part is handled automatically by the **Frontend SDK**. +You don not need to do anything. +The step serves more as a way for us to tell you how is this handled under the hood. + +After you call the `init` function, the **SDK** will add interceptors to both `fetch` and `XHR`, XMLHTTPRequest. The latter is used by the `axios` library. +The interceptors save the session tokens that are generated from the authentication flow. +Those tokens are then added to requests initialized by your frontend app which target the backend API. +By default, the tokens are stored through session cookies but you can also switch to [header based authentication](../common-customizations/sessions/token-transfer-method). + + + +## 5. Secure Application Routes + +In order to prevent unauthorized access to ceratain parts of your frontend application you can use our utilities. +Follow the code samples below to understand how to do this. + + + + + +You can wrap your components with the `` react component. This will ensure that your component renders only if the user is logged in. If they are not logged in, the user will be redirected to the login page. + +```tsx +import React from "react"; +import { + BrowserRouter, + Routes, + Route, +} from "react-router-dom"; +// highlight-next-line +import { SessionAuth } from "supertokens-auth-react/recipe/session"; +// @ts-ignore +import MyDashboardComponent from "./dashboard"; + +class App extends React.Component { + render() { + return ( + + + + {/*Components that require to be protected by authentication*/} + + + // highlight-end + } /> + + + ); + } +} +``` + + + + + +You can use the `doesSessionExist` function to check if a session exists in all your routes. + +```tsx +import Session from 'supertokens-web-js/recipe/session'; + +async function doesSessionExist() { + if (await Session.doesSessionExist()) { + // user is logged in + } else { + // user has not logged in yet + } +} +``` + + + + + +## 6. View the login UI + + + +You can check the login UI by visiting the `^{form_websiteBasePath}` route, in your frontend application. +To review all the components of our pre-built UI please follow [this link](https://master--6571be2867f75556541fde98.chromatic.com/?path=/story/auth-page--playground). + + + + + + +
+ + + +This guide shows you how to create your own UI on top of the **SuperTokens SDK**. +If you want to use our **Pre Built Components** please check the following tutorial. + +## 1. Install the SDK + + + +## 2. Initialize the SDK + +Call the SDK init function at the start of your application. +The invocation includes the [main configuration details](../appinfo), as well as the **recipes** that you will be using in your setup. + + + + + + + + + +```tsx +import SuperTokens from 'supertokens-web-js'; +import Session from 'supertokens-web-js/recipe/session'; +import Passwordless from 'supertokens-web-js/recipe/passwordless' + +SuperTokens.init({ + appInfo: { + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}", + appName: "...", + }, + recipeList: [ + Session.init(), + Passwordless.init(), + ], +}); +``` + + + + + + + + + +```tsx +import supertokens from "supertokens-web-js-script"; +import supertokensSession from "supertokens-web-js-script/recipe/session"; +import supertokensPasswordless from 'supertokens-web-js-script/recipe/passwordless' +supertokens.init({ + appInfo: { + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}", + appName: "...", + }, + recipeList: [ + supertokensSession.init(), + supertokensPasswordless.init(), + ], +}); +``` + + + + + + + + + + + + + + + + + +```tsx +import SuperTokens from 'supertokens-react-native'; + +SuperTokens.init({ + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}", +}); +``` + + + + + + + + + +Add the `SuperTokens.init` function call at the start of your application. + +```kotlin +import android.app.Application +import com.supertokens.session.SuperTokens + +class MainApplication: Application() { + override fun onCreate() { + super.onCreate() + + SuperTokens.Builder(this, "^{form_apiDomain}") + .apiBasePath("^{form_apiBasePath}") + .build() + } +} +``` + + + + + + + + + + +```swift +import UIKit +import SuperTokensIOS + +fileprivate class ApplicationDelegate: UIResponder, UIApplicationDelegate { + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + do { + try SuperTokens.initialize( + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}" + ) + } catch SuperTokensError.initError(let message) { + // TODO: Handle initialization error + } catch { + // Some other error + } + + return true + } + +} +``` + + + + + + + + + +```dart +import 'package:supertokens_flutter/supertokens.dart'; + +void main() { + SuperTokens.init( + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}", + ); +} +``` + + + + + + + + + + + + +## 3. Add the Login UI + + + +## 4. Handle Session Tokens + + + +## 5. Protect Frontend Routes + + + +## 6. Add a Sign Out Action + + + + + + + +
+ + +:::success 🎉 Congratulations 🎉 + +Congratulations! You've successfully integrated your frontend app with SuperTokens. + +The [next section](./backend-setup) will guide you through setting up your backend and then you should be able to complete a login flow. + +::: diff --git a/v2/passwordless/quickstart/introduction.mdx b/v2/passwordless/quickstart/introduction.mdx new file mode 100644 index 000000000..999ee8d87 --- /dev/null +++ b/v2/passwordless/quickstart/introduction.mdx @@ -0,0 +1,85 @@ +--- +id: introduction +title: Introduction +hide_title: true +show_ui_switcher: false +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + +# Introduction + +## Overview + +This quickstart will guide you through how to set up a basic project that uses **SuperTokens** to authenticate users. +The tutorial shows a **Passwordless** login flow, rendered by either our **Prebuilt UI components** or by your own **Custom UI**. + + + + + +If you want to skip straight to an example application you can choose between: +- Checking our live [demo application](https://^{docsLinkRecipeName}.demo.supertokens.com/auth) +- Running a **SuperTokens** project from your local machine. You just have to use our CLI app and execute the following command: + +```bash +npx create-supertokens-app@latest --recipe=^{docsLinkRecipeName} +``` + + + + +## Before you start + + + + + +Before going into the actual tutorial, let's get a clear picture of how **SuperTokens** works and some of the terms that we will use throughout the documentation. + +### SuperTokens Core + +The main service that provides all the functionality is called the **SuperTokens Core**. SDKs communicate over an API with this service in order to +perform authentication related tasks. + +Unlike with other providers, the **SuperTokens Frontend SDK** never talks to the **Authentication Service** directly. +All the requests target your existing **Backend Service**. +From there, the **Backend SDKs** are used to expose new authentication routes. Those in turn communicate with the **SuperTokens Core**. + +You can check the following diagram for a high level overview of how the services will interact within an authentication setup that involves **SuperTokens**. + + + + Flowchart of architecture when using SuperTokens managed service + + + Flowchart of architecture when self-hosting SuperTokens + + + +:::info Edge Cases +- You can also host the **SuperTokens Core** yourself. In that case your backend will communicate with a service that exists inside your infrastructure. +- If you are using a backend for which we do not have an SDK, you will have to spin up an additional auth service in a language for which we do have a backend SDK (NodeJS, Python or Golang). +::: + + +### Recipes + +The functionalities that **SuperTokens** provides are bundled into objects that can be reffered to as **Recipes**. +Everything from *authentication methods* to *session and user management* can be included under this concept. +In the following sections, we will see how recipes get initialised and configured and how you can customise them to fit your use case. + + +Now that we have cleared all this out, we can move forward with the actual tutorial. +Go to the next page to see how to configure your [Frontend Application](./frontend-setup). + + + diff --git a/v2/passwordless/quickstart/next-steps.mdx b/v2/passwordless/quickstart/next-steps.mdx new file mode 100644 index 000000000..2765d260a --- /dev/null +++ b/v2/passwordless/quickstart/next-steps.mdx @@ -0,0 +1,177 @@ +--- +id: next-steps +title: Next Steps +hide_title: true +show_ui_switcher: false +show_next_button: false +--- + +import Card from "/src/components/card/Card" +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +import PreBuiltUIInjector from "/src/components/prebuiltuiInjector" +import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; +import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" +import {Question, Answer}from "/src/components/question" +import RRDVersionSubTabs from "/src/components/tabs/RRDVersionSubTabs" +import AppInfoForm from "/src/components/appInfoForm" +import NodeJSFrameworkSubTabsServerless from "/src/components/tabs/NodeJSFrameworkSubTabsServerless" +import CoreInjector from "/src/components/coreInjector" +import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs" +import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" +import BackendSDKCasing from "/src/components/BackendSDKCasing" + + + +# Next Steps + + + + + +## Overview + +Now that you have completed the quickstart guide there are a few more things that you need to take care of on the road towards a production ready authentication experience. + +## Configure the Core Service + +If you have signed up and deployed a SuperTokens environment already, you can skip this step. +Otherwise, please follow these instructions to use the correct **SuperTokens Core** instance in your application. + +The steps show you how to connect to a **SuperTokens Managed Service Environment**. +If you want to self host the core instance please check the [following guide](../pre-built-ui/setup/core/with-docker). + +### 1. Sign up for a SuperTokens account + +Open this [page](https://supertokens.com/auth) in order to access the account creation page. +Select the account that you want to use and wait for the action to complete. + +### 2. Select the authentication method + +After you have created your account, you will be prompted with a form that will ask you to specify details about your configuration. +Select which authentication method that you want to use. + +Integration with SuperTokens SDKs + +### 3. Select the region where you want to deploy the core service# + +SuperTokens environments can be deployed in 3 different regions: `US East (N. Virginia)`, `Europe (Ireland)`, `Asia Pacific (Singapore)`. +In order to avoid any latency issues please select a region that is closest to where your services are hosted. + +### 4. Click the deploy button 🚀 + +Integration with SuperTokens SDKs + +Our internal service will deploy a separate environment based on your selection. + +After this process is complete, you will be directed to the dashboard page. Here you can view and edit information about your newly created environment. + +The main thing that we want to focus is the **Connecting to a development instance** section. +There you can see two different values, the `connectionURI` and the `apiKey`. +You will use these values in the next step. + +:::info + +The initial setup flow only configures a development environment. In order to use SuperTokens in production, you will have to click the Create Production Env button. + +::: + +### 5. Connect the Backend SDK with SuperTokens 🔌 + +Add the `connectionURI` and the `apiKey` shown on the dashboard to your code on the backend. + +SuperTokens managed service dashboard connectionURI and API key + + + + +```tsx +import supertokens from "supertokens-node"; + +supertokens.init({ + // highlight-start + supertokens: { + connectionURI: "", + apiKey: "" + }, + // highlight-end + appInfo: { + apiDomain: "...", + appName: "...", + websiteDomain: "..." + }, + recipeList: [] +}); +``` + + + + +```go +import "github.com/supertokens/supertokens-golang/supertokens" + +func main() { + supertokens.Init(supertokens.TypeInput{ + // highlight-start + Supertokens: &supertokens.ConnectionInfo{ + ConnectionURI: "", + APIKey: "", + }, + // highlight-end + }) +} + +``` + + + + +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig + +init( + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + # highlight-start + supertokens_config=SupertokensConfig( + connection_uri='', + api_key='' + ), + # highlight-end + framework='...', # type: ignore + recipe_list=[ + #... + ] +) +``` + + + + + + +## Customize Your Authentication Flow + +After you have connected the Backend SDK to a specific **SuperTokens Core Service**, you can go check the rest of the documentation. +There are several sections that show you how to customize the authentication experience to fit your specific needs. +Some of the most common subjects are: + +- [**Add Email Verification**](../common-customizations/email-verification/about) +- [**Add a Custom Redirect Action**](../pre-built-ui/auth-redirection) +- [**Use Custom Session Management**](../common-customizations/sessions/session-verification-in-api/get-session) +- [**Share Sessions Across Subdomains**](../common-customizations/sessions/share-sessions-across-sub-domains) +- [**Post Sign In Actions**](../common-customizations/handling-signinup-success) + + +## Explore Additional Features + +You can also review the additional features that **SuperTokens** exposes. +Those can help you extend the authentication implementation on different levels. + +- [**Self Host SuperTokens Core**](../pre-built-ui/setup/core/with-docker) +- [**Migration Guide**](../migration/about) +- [**Multi Factor Authentication**](../../mfa/introduction) +- [**Manage Users through the User Management Dashboard**](../pre-built-ui/setup/user-management-dashboard/setup) +- [**Multi Tenancy**](../../multitenancy/introduction) +- [**User Roles and Permissions**](../user-roles/initialisation) + diff --git a/v2/passwordless/sidebars.js b/v2/passwordless/sidebars.js index b70ecb2ef..21a84e12e 100644 --- a/v2/passwordless/sidebars.js +++ b/v2/passwordless/sidebars.js @@ -1,158 +1,35 @@ module.exports = { sidebar: [ { + label: "quickstart", type: "category", - label: "Start Here", - collapsed: false, customProps: { highlightGroup: true, }, + collapsed: false, items: [ - "introduction", { - type: "category", - label: "Quick setup with Pre built UI", - customProps: { - categoryIcon: "lightning", - }, - items: [ - { - type: "category", - label: "Setup", - collapsed: false, - items: [ - "pre-built-ui/setup/frontend", - "pre-built-ui/setup/backend", - { - type: "category", - label: "Step 3: Core", - items: [ - { - type: "category", - label: "Self hosted", - items: [ - "pre-built-ui/setup/core/with-docker", - "pre-built-ui/setup/core/without-docker", - { - type: "category", - label: "Database Setup", - items: [ - "pre-built-ui/setup/database-setup/mysql", - "pre-built-ui/setup/database-setup/postgresql", - "pre-built-ui/setup/database-setup/rename-database-tables", - ], - }, - ], - }, - "pre-built-ui/setup/core/saas-setup", - ], - }, - { - type: "category", - label: "Step 4: User management dashboard", - items: [ - "pre-built-ui/setup/user-management-dashboard/setup", - "pre-built-ui/setup/user-management-dashboard/users-listing-and-details", - "pre-built-ui/setup/user-management-dashboard/managing-user-roles-and-permissions", - { - type: "category", - label: "Tenant Management", - collapsed: true, - items: [ - "pre-built-ui/setup/user-management-dashboard/tenant-management/overview", - "pre-built-ui/setup/user-management-dashboard/tenant-management/details", - ], - }, - ], - }, - ], - }, - "pre-built-ui/handling-session-tokens", - "pre-built-ui/securing-routes", - "pre-built-ui/sign-out", - "pre-built-ui/auth-redirection", - "pre-built-ui/enable-email-verification", - "pre-built-ui/multitenant-login", - { - type: "category", - label: "Further Reading", - items: [ - "pre-built-ui/further-reading/passwordless-login", - "pre-built-ui/further-reading/email-verification", - ], - }, - ], + id: "quickstart/introduction", + type: "doc", + label: "Introduction", }, { - type: "category", - label: "Using your own UI / Custom UI", - customProps: { - categoryIcon: "pencil", - }, - items: [ - { - type: "category", - label: "Initialisation", - collapsed: false, - items: [ - "custom-ui/init/frontend", - "custom-ui/init/backend", - { - type: "category", - label: "Step 3: Core", - items: [ - { - type: "category", - label: "Self hosted", - items: [ - "custom-ui/init/core/with-docker", - "custom-ui/init/core/without-docker", - { - type: "category", - label: "Database Setup", - items: [ - "custom-ui/init/database-setup/mysql", - "custom-ui/init/database-setup/postgresql", - "custom-ui/init/database-setup/rename-database-tables", - ], - }, - ], - }, - "custom-ui/init/core/saas-setup", - ], - }, - { - type: "category", - label: "Step 4: User management dashboard", - items: [ - "custom-ui/init/user-management-dashboard/setup", - "custom-ui/init/user-management-dashboard/users-listing-and-details", - "custom-ui/init/user-management-dashboard/managing-user-roles-and-permissions", - { - type: "category", - label: "Tenant Management", - collapsed: true, - items: [ - "custom-ui/init/user-management-dashboard/tenant-management/overview", - "custom-ui/init/user-management-dashboard/tenant-management/details", - ], - }, - ], - }, - ], - }, - "custom-ui/login-magic-link", - "custom-ui/login-otp", - "custom-ui/handling-session-tokens", - "custom-ui/securing-routes", - "custom-ui/sign-out", - "custom-ui/enable-email-verification", - "custom-ui/multitenant-login", - ], + id: "quickstart/frontend-setup", + type: "doc", + label: "Frontend Setup", + }, + { + id: "quickstart/backend-setup", + type: "doc", + label: "Backend Setup", + }, + { + id: "quickstart/next-steps", + type: "doc", + label: "Next Steps", }, ], }, - "user-object", { type: "category", label: "Integrations", @@ -372,6 +249,17 @@ module.exports = { "common-customizations/sessions/protecting-frontend-routes", "common-customizations/sessions/with-jwt/read-jwt", "common-customizations/sessions/ssr", + { + id: "pre-built-ui/handling-session-tokens", + type: "doc", + label: "Access Session Tokens", + }, + { id: "pre-built-ui/sign-out", type: "doc", label: "Add Sign Out" }, + { + id: "pre-built-ui/auth-redirection", + type: "doc", + label: "Add Redirect Actions", + }, { type: "category", label: "Reading / modifying session claims", @@ -547,6 +435,28 @@ module.exports = { }, "common-customizations/multiple-clients", "common-customizations/userid-format", + { + id: "pre-built-ui/setup/core/saas-setup", + label: "Connecting to the SuperTokens Core Managed Service", + type: "doc", + }, + { + type: "category", + label: "Self Hosting SuperTokens Core", + items: [ + "pre-built-ui/setup/core/with-docker", + "pre-built-ui/setup/core/without-docker", + { + type: "category", + label: "Database Setup", + items: [ + "pre-built-ui/setup/database-setup/mysql", + "pre-built-ui/setup/database-setup/postgresql", + "pre-built-ui/setup/database-setup/rename-database-tables", + ], + }, + ], + }, { type: "category", label: @@ -669,6 +579,24 @@ module.exports = { "mfa", "multi-tenant", "attack-protection-suite", + { + type: "category", + label: "User Management dashboard", + items: [ + "pre-built-ui/setup/user-management-dashboard/setup", + "pre-built-ui/setup/user-management-dashboard/users-listing-and-details", + "pre-built-ui/setup/user-management-dashboard/managing-user-roles-and-permissions", + { + type: "category", + label: "Tenant Management", + collapsed: true, + items: [ + "pre-built-ui/setup/user-management-dashboard/tenant-management/overview", + "pre-built-ui/setup/user-management-dashboard/tenant-management/details", + ], + }, + ], + }, ], }, "scalability", @@ -709,11 +637,20 @@ module.exports = { items: [ "architecture", "other-frameworks", + "user-object", "flow_diagram", "appinfo", "sdks", "apis", "compatibility-table", + { + type: "category", + label: "Prebuilt UI Components", + items: [ + "pre-built-ui/further-reading/passwordless-login", + "pre-built-ui/further-reading/email-verification", + ], + }, ], }, ], diff --git a/v2/session/common-customizations/sessions/protecting-frontend-routes.mdx b/v2/session/common-customizations/sessions/protecting-frontend-routes.mdx index 9e5719247..7a0f1c394 100644 --- a/v2/session/common-customizations/sessions/protecting-frontend-routes.mdx +++ b/v2/session/common-customizations/sessions/protecting-frontend-routes.mdx @@ -291,133 +291,6 @@ async function shouldLoadRoute(): Promise { - - - - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```tsx -import Session from 'supertokens-web-js/recipe/session'; - -async function doesSessionExist() { - if (await Session.doesSessionExist()) { - // user is logged in - } else { - // user has not logged in yet - } -} -``` - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```tsx -import Session from 'supertokens-web-js-script/recipe/session'; -async function doesSessionExist() { - if (await Session.doesSessionExist()) { - // user is logged in - } else { - // user has not logged in yet - } -} -``` - - - - - - - - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```tsx -import SuperTokens from 'supertokens-react-native'; - -async function doesSessionExist() { - if (await SuperTokens.doesSessionExist()) { - // user is logged in - } else { - // user has not logged in yet - } -} -``` - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```kotlin -import android.app.Application -import com.supertokens.session.SuperTokens - -class MainApplication: Application() { - fun doesSessionExist() { - if (SuperTokens.doesSessionExist(this.applicationContext)) { - // user is logged in - } else { - // user has not logged in yet - } - } -} -``` - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```swift -import UIKit -import SuperTokensIOS - -fileprivate class ViewController: UIViewController { - func doesSessionExist() { - if SuperTokens.doesSessionExist() { - // User is logged in - } else { - // User is not logged in - } - } -} -``` - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```dart -import 'package:supertokens_flutter/supertokens.dart'; - -Future doesSessionExist() async { - return await SuperTokens.doesSessionExist(); -} -``` - - - - - - - - - ## Verifying the claims of a session diff --git a/v2/session/quick-setup/handling-session-tokens.mdx b/v2/session/quick-setup/handling-session-tokens.mdx index bdfa1a21f..6ea8a485e 100644 --- a/v2/session/quick-setup/handling-session-tokens.mdx +++ b/v2/session/quick-setup/handling-session-tokens.mdx @@ -6,410 +6,3 @@ 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"; -import AppInfoForm from "/src/components/appInfoForm" -import {Question, Answer}from "/src/components/question" - -# Handling session tokens - -There are two modes ways in which you can use sessions with SuperTokens: -- Using `httpOnly` cookies -- Authorization bearer token. - -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 - -### For Web - - - - - -:::success -No action required. -::: - -Our frontend SDK handles everything for you. You only need to make sure that you have called `supertokens.init` before making any network requests. - -Our SDK adds interceptors to `fetch` and `XHR` (used by `axios`) to save and add session tokens from and to the request. - -By default, our web SDKs use cookies to provide credentials. - - - - - -import NetworkInterceptors from "/session/reusableMD/networkInterceptors.mdx" - -### For React-Native -Our frontend SDK handles everything for you. You only need to make sure that you have added our network interceptors as shown below - - - -:::note -By default our mobile SDKs use a bearer token in the Authorization header to provide credentials. -::: - -### For Android - - - - -```kotlin -import android.app.Application -import com.supertokens.session.SuperTokens -import com.supertokens.session.SuperTokensHttpURLConnection -import com.supertokens.session.SuperTokensPersistentCookieStore -import java.net.URL -import java.net.HttpURLConnection - -class MainApplication: Application() { - override fun onCreate() { - super.onCreate() - // TODO: Make sure to call SuperTokens.init - } - - fun makeRequest() { - val url = URL("") - val connection = SuperTokensHttpURLConnection.newRequest(url, object: SuperTokensHttpURLConnection.PreConnectCallback { - override fun doAction(con: HttpURLConnection?) { - // TODO: Use `con` to set request method, headers etc - } - }) - - // Handle response using connection object, for example: - if (connection.responseCode == 200) { - // TODO: implement - } - } -} -``` - -:::note -When making network requests you do not need to call `HttpURLConnection.connect` because SuperTokens does this for you. -::: - - - - -```kotlin -import android.content.Context -import com.supertokens.session.SuperTokens -import com.supertokens.session.SuperTokensInterceptor -import okhttp3.OkHttpClient -import retrofit2.Retrofit - -class NetworkManager { - fun getClient(context: Context): OkHttpClient { - val clientBuilder = OkHttpClient.Builder() - clientBuilder.addInterceptor(SuperTokensInterceptor()) - // TODO: Make sure to call SuperTokens.init - - val client = clientBuilder.build() - - // REQUIRED FOR RETROFIT ONLY - val instance = Retrofit.Builder() - .baseUrl("") - .client(client) - .build() - - return client - } - - fun makeRequest(context: Context) { - val client = getClient(context) - // Use client to make requests normally - } -} -``` - - - - -:::note -By default our mobile SDKs use a bearer token in the Authorization header to provide credentials. -::: - -### For iOS - - - - - -

Using URLSession.shared

- -```swift -import Foundation -import SuperTokensIOS - -fileprivate class NetworkManager { - func setupSuperTokensInterceptor() { - URLProtocol.registerClass(SuperTokensURLProtocol.self) - } -} -``` - -

Using a custom URLSession instance

- -```swift -import Foundation -import SuperTokensIOS - -fileprivate class NetworkManager { - func setupSuperTokensInterceptor() { - let configuration = URLSessionConfiguration.default - configuration.protocolClasses = [SuperTokensURLProtocol.self] - let session = URLSession(configuration: configuration) - - // Use session when making network requests - } -} -``` - -
- - - -```swift -import Foundation -import SuperTokensIOS -import Alamofire - -fileprivate class NetworkManager { - func setupSuperTokensInterceptor() { - let configuration = URLSessionConfiguration.af.default - configuration.protocolClasses = [SuperTokensURLProtocol.self] + (configuration.protocolClasses ?? []) - let session = Session(configuration: configuration) - - // Use session when making network requests - } -} -``` - - - -
- -:::note -By default our mobile SDKs use a bearer token in the Authorization header to provide credentials. -::: - -### For Flutter - - - - -You can make requests as you normally would with `http`, the only difference is that you import the client from the supertokens package instead. - -```dart -// Import http from the SuperTokens package -import 'package:supertokens_flutter/http.dart' as http; - -Future makeRequest() async { - Uri uri = Uri.parse("http://localhost:3001/api"); - var response = await http.get(uri); - // handle response -} -``` - -

Using a custom http client

- -If you use a custom http client and want to use SuperTokens, you can simply provide the SDK with your client. All requests will continue to use your client along with the session logic that SuperTokens provides. - -```dart -// Import http from the SuperTokens package -import 'package:supertokens_flutter/http.dart' as http; - -Future makeRequest() async { - Uri uri = Uri.parse("http://localhost:3001/api"); - - // Initialise your custom client - var customClient = http.Client(); - // provide your custom client to SuperTokens - var httpClient = http.Client(client: customClient); - - var response = await httpClient.get(uri); - // handle response -} -``` - -
- - -

Add the SuperTokens interceptor

- -Use the extension method provided by the SuperTokens SDK to enable interception on your Dio client. This allows the SuperTokens SDK to handle session tokens for you. - -```dart -import 'package:supertokens_flutter/dio.dart'; -import 'package:dio/dio.dart'; - -void setup() { - Dio dio = Dio(); // Create a Dio instance. - dio.addSupertokensInterceptor(); -} -``` - -

Making network requests

- -You can make requests as you normally would with `dio`. - -```dart -import 'package:supertokens_flutter/dio.dart'; -import 'package:dio/dio.dart'; - -void setup() { - Dio dio = Dio( - // Provide your config here - ); - dio.addSupertokensInterceptor(); - - var response = dio.get("http://localhost:3001/api"); - // handle response -} -``` - -
-
- -:::note -By default our mobile SDKs use a bearer token in the Authorization header to provide credentials. -::: - -## If not using our frontend SDK - -:::caution -We highly recommend using our frontend SDK to handle session token management. It will save you a lot of time. -::: - -In this case, you will need to manually handle the tokens and session refreshing, and decide if you are going to use header or cookie-based sessions. - -For browsers, we recommend cookies, while for mobile apps (or if you don't want to use the built-in cookie manager) you should use header-based sessions. - - - - - -### On login - -You should attach the `st-auth-mode` header to calls to the login API, but this header is safe to attach to all requests. In this case it should be set to "cookie". - -The login API will return the following headers: -- `Set-Cookie`: This will contain the `sAccessToken`, `sRefreshToken` cookies which will be `httpOnly` and will be automatically mananaged by the browser. For mobile apps, you will need to setup cookie handling yourself, use our SDK or use a header based authentication mode. -- `front-token` header: This contains information about the access token: - - The userID - - The expiry time of the access token - - The payload added by you in the access token. - - Here is the structure of the token: - ```tsx - let frontTokenFromRequestHeader = "..."; - let frontTokenDecoded = JSON.parse(decodeURIComponent(escape(atob(frontTokenFromRequestHeader)))); - console.log(frontTokenDecoded); - /* - { - ate: 1665226412455, // time in milliseconds for when the access token will expire, and then a refresh is required - uid: "....", // user ID - up: { - sub: "..", - iat: .., - ... // other access token payload - } - } - - */ - ``` - - This token is mainly used for cookie based auth because you don't have access to the actual access token on the frontend (for security reasons), but may want to read its payload (for example to know the user's role). This token itself is not signed and hence can't be used in place of the access token itself. You may want to save this token in localstorage or in frontend cookies (using `document.cookies`). - -- `anti-csrf` header (optional): By default it's not required, so it's not sent. But if this is sent, you should save this token as well for use when making requests. - -### Making network requests to protected APIs - -The `sAccessToken` will get attached to the request automatically by the browser. Other than that, you need to add the following headers to the request: -- `rid: "anti-csrf"` - this prevents against anti-CSRF requests. If your `apiDomain` and `websiteDomain` values are exactly the same, then this is not necessary. -- `anti-csrf` header (optional): If this was provided to you during login, then you need to add that token as the value of this header. -- You need to set the `credentials` header to `true` or `include`. This is achieved different based on the library you are using to make requests. - -An API call can potentially update the `sAccessToken` and `front-token` tokens, for example if you call the `mergeIntoAccessTokenPayload` function on the `session` object on the backend. This kind of update is reflected in the response headers for your API calls. The headers will contain new values for: -- `sAccessToken`: This will be as a new `Set-Cookie` header and will be managed by the browser automatically. -- `front-token`: This should be read and saved by you in the same way as it's being done during login. - -### Handling session refreshing -If any of your API calls return with a status code of `401`, it means that the access token has expired. This will require you to refresh the session before retrying the same API call. - -You can call the refresh API as follows: - - - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/session/refresh' \ ---header 'Cookie: sRefreshToken=...' -``` - -:::note -- You may also need to add the `anti-csrf` header to the request if that was provided to you during sign in. -- The cURL command above shows the `sRefreshToken` cookie as well, but this is added by the web browser automatically, so you don't need to add it explicitly. -::: - - - -The result of a session refresh will be either: -- Status code `200`: This implies a succesful refresh. The set of tokens returned here will be the same as when the user logs in, so you can handle them in the same way. -- Status code `401`: This means that the refresh token is invalid, or has been revoked. You must ask the user to relogin. Remember to clear the `front-token` that you saved on the frontend earlier. - - - - - -### On login - -You should attach the `st-auth-mode` header to calls to the login API, but this header is safe to attach to all requests. In this case it should be set to "header". - -The login API will return the following headers: -- `st-access-token`: This contains the current access token associated with the session. You should save this in your application (e.g., in frontend localstorage). -- `st-refresh-token`: This contains the current refresh token associated with the session. You should save this in your application (e.g., in frontend localstorage). - -### Making network requests to protected APIs - -You need to add the following headers to request: -- `authorization: Bearer {access-token}` -- You need to set the `credentials` to `true` or `include`. This is achieved different based on the library you are using to make requests. - -An API call can potentially update the `access-token`, for example if you call the `mergeIntoAccessTokenPayload` function on the `session` object on the backend. This kind of update is reflected in the response headers for your API calls. The headers will contain new values for `st-access-token` - -These should be read and saved by you in the same way as it's being done during login. - -### Handling session refreshing -If any of your API calls return with a status code of `401`, it means that the access token has expired. This will require you to refresh the session before retrying the same API call. - -You can call the refresh API as follows: - - - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/session/refresh' \ ---header 'authorization: Bearer {refresh-token}' -``` - - - -The result of a session refresh will be either: -- Status code `200`: This implies a succesful refresh. The set of tokens returned here will be the same as when the user logs in, so you can handle them in the same way. -- Status code `401`: This means that the refresh token is invalid, or has been revoked. You must ask the user to relogin. Remember to clear the `st-refresh-token` and `st-access-token` that you saved on the frontend earlier. - - - - diff --git a/v2/src/components/button/Button.css b/v2/src/components/button/Button.css new file mode 100644 index 000000000..43ae209a0 --- /dev/null +++ b/v2/src/components/button/Button.css @@ -0,0 +1,54 @@ +.st-button { + padding-left: 1.25rem; + padding-right: 1.25rem; + padding-top: 0.3125rem; + padding-bottom: 0.3125rem; + display: inline-flex; + align-items: center; + justify-content: center; + font-weight: 500; + border-radius: 0.5rem; + font-size: 16px; + font-weight: 600; + text-decoration: none !important; + cursor: pointer; + + &:hover { + transition: opacity 0.2s ease-in-out; + } + + &:disabled { + cursor: default; + background: #ffbf80; + } +} + +.st-button--primary { + border: 1px solid #ff9933; + background-color: #ff9933; + color: #ffffff; + &:hover { + background-color: #ff8d1c; + color: #ffffff; + } +} + +.st-button--light { + border: 1px solid #ff9933; + background-color: #ffcc99; + color: #cc6600; + &:hover { + color: #cc6600; + opacity: 0.8; + } +} + +.st-button--ghost { + border: 1px solid #ff9933; + background-color: transparent; + color: #ff9933; + &:hover { + color: #ff9933; + opacity: 0.8; + } +} diff --git a/v2/src/components/button/Button.tsx b/v2/src/components/button/Button.tsx new file mode 100644 index 000000000..c21489f38 --- /dev/null +++ b/v2/src/components/button/Button.tsx @@ -0,0 +1,68 @@ +import React from "react"; + +import "./Button.css"; + +type BaseProps = { + type?: "primary" | "light" | "ghost"; + children: React.ReactNode; + className?: string; + disabled?: boolean; +}; + +type LinkButton = BaseProps & { + as?: "a"; + href: string; +}; + +type ActionButton = BaseProps & { + as?: "button"; + onClick?: () => void; +}; + +type ButtonProps = LinkButton | ActionButton; + +const Button = React.forwardRef< + HTMLButtonElement | HTMLAnchorElement, + ButtonProps +>((props, ref) => { + const { + type = "primary", + children, + className, + disabled = false, + as = "button", + ...rest + } = props; + + const buttonClassName = `st-button st-button--${type} ${className || ""}`; + + if (as === "a") { + const typedRef = ref as React.RefObject; + return ( + + {children} + + ); + } + + const typedRef = ref as React.RefObject; + return ( + + ); +}); + +Button.displayName = "Button"; + +export default Button; diff --git a/v2/src/components/card/Card.css b/v2/src/components/card/Card.css new file mode 100644 index 000000000..00c376a8b --- /dev/null +++ b/v2/src/components/card/Card.css @@ -0,0 +1,64 @@ +.st-card { + display: flex; + align-items: center; + + background: #232323; + border: 1px solid #2e2e2e; + border-radius: 13px; + + padding: 1.5em; + + font-style: normal; + font-weight: 600; + font-size: 1rem; + color: #b2b2b2; + + text-decoration: none !important; + + position: relative; + overflow: hidden; + + transition: all 0.2s ease-in-out; + + &:hover { + border: 1px solid rgba(255, 153, 51, 0.3); + color: #b2b2b2 !important; + } +} + +.st-card__icon { + background: #343434; + + border-radius: 7px; + padding: 8px; + height: 4rem; + width: 4rem; + + display: flex; + justify-content: center; + align-items: center; + + margin-right: 0.75em; +} + +.recipe_box::before { + content: " "; + position: absolute; + border-radius: 100%; + top: 0; + left: 0; + height: 50px; + width: 50px; + + background: #a9a9a9; + filter: blur(68.5px); +} + +.st-card__list { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr)); + gap: 20px; + list-style-type: none; + padding: 0; + margin: 0; +} diff --git a/v2/src/components/card/Card.tsx b/v2/src/components/card/Card.tsx new file mode 100644 index 000000000..6f8d26d9c --- /dev/null +++ b/v2/src/components/card/Card.tsx @@ -0,0 +1,43 @@ +import React, { useState } from "react"; +import "./Card.css"; + +function CardRoot({ + children, + path, + style = {}, +}: { + children: React.ReactNode; + path: string; + style?: React.CSSProperties; +}) { + return ( + + {children} + + ); +} + +function CardList({ children }: { children: React.ReactNode }) { + return
    {children}
; +} + +function CardIcon({ icon, alt }: { icon: string; alt: string }) { + const iconPath = `/img/guides/${icon}.svg`; + return ( +
+ {alt} +
+ ); +} + +function CardTitle({ children }: { children: React.ReactNode }) { + return
{children}
; +} + +const Card = Object.assign(CardRoot, { + List: CardList, + Icon: CardIcon, + Title: CardTitle, +}); + +export default Card; diff --git a/v2/src/components/preBuiltOrCustomUISwitcher/index.tsx b/v2/src/components/preBuiltOrCustomUISwitcher/index.tsx index c86f314eb..f1e15964d 100644 --- a/v2/src/components/preBuiltOrCustomUISwitcher/index.tsx +++ b/v2/src/components/preBuiltOrCustomUISwitcher/index.tsx @@ -4,89 +4,116 @@ const preBuiltValue = "prebuilt"; const customUIValue = "custom"; const uiStorageKey = "ui"; -export function getUIModeFromStorage(): typeof preBuiltValue | typeof customUIValue { - if (typeof window === "undefined") { - return preBuiltValue; - } - let uiFromStorage = window.localStorage.getItem(uiStorageKey); - - if (uiFromStorage === null || (uiFromStorage !== preBuiltValue && uiFromStorage !== customUIValue)) { - uiFromStorage = preBuiltValue; - // we do not want to call updateUIMode cause this will be called on page refresh - // for each tab anyway. - window.localStorage.setItem(uiStorageKey, uiFromStorage); - } - - return uiFromStorage as any; +export function getUIModeFromStorage(): + | typeof preBuiltValue + | typeof customUIValue { + if (typeof window === "undefined") { + return preBuiltValue; + } + let uiFromStorage = window.localStorage.getItem(uiStorageKey); + + if ( + uiFromStorage === null || + (uiFromStorage !== preBuiltValue && uiFromStorage !== customUIValue) + ) { + uiFromStorage = preBuiltValue; + // we do not want to call updateUIMode cause this will be called on page refresh + // for each tab anyway. + window.localStorage.setItem(uiStorageKey, uiFromStorage); + } + + return uiFromStorage as any; +} + +export function updateUIMode( + value: typeof preBuiltValue | typeof customUIValue, +) { + if (typeof window === "undefined") { + return; + } + window.localStorage.setItem(uiStorageKey, value); + window.dispatchEvent(new Event("uiModeChanged")); +} + +export function PrebuiltUILink({ children }: React.PropsWithChildren<{}>) { + return ( + updateUIMode("prebuilt")}> + {children} + + ); } -export function updateUIMode(value: typeof preBuiltValue | typeof customUIValue) { - if (typeof window === "undefined") { - return; - } - window.localStorage.setItem(uiStorageKey, value); - window.dispatchEvent(new Event("uiModeChanged")); +export function CustomUILink({ children }: React.PropsWithChildren<{}>) { + return ( + updateUIMode("custom")}> + {children} + + ); } export function PreBuiltOrCustomUISwitcher(props: any) { - let [uiMode, setUIMode] = useState(getUIModeFromStorage()); + let [uiMode, setUIMode] = useState(getUIModeFromStorage()); - const children = React.Children.map(props.children, child => { - return React.cloneElement(child, { - selectedvalue: uiMode, - }); + const children = React.Children.map(props.children, (child) => { + return React.cloneElement(child, { + selectedvalue: uiMode, }); + }); - const onUIModeChanged = () => { - setUIMode(getUIModeFromStorage()); - } - - useEffect(() => { - window.addEventListener("uiModeChanged", onUIModeChanged); - return () => { - window.removeEventListener("uiModeChanged", onUIModeChanged); - } - }, []) - - return ( -
- {children} -
- ); + const onUIModeChanged = () => { + setUIMode(getUIModeFromStorage()); + }; + + useEffect(() => { + window.addEventListener("uiModeChanged", onUIModeChanged); + return () => { + window.removeEventListener("uiModeChanged", onUIModeChanged); + }; + }, []); + + return ( +
+ {children} +
+ ); } export function PreBuiltUIContent(props: any) { - return ; + return ; } export function CustomUIContent(props: any) { - return ; + return ; } function PreBuiltCustomUITabChild(props: { - value: typeof preBuiltValue | typeof customUIValue, - selectedvalue: string, - children: any, + value: typeof preBuiltValue | typeof customUIValue; + selectedvalue: string; + children: any; }) { - const { value, selectedvalue, children } = props; - - if (value !== selectedvalue) { - return null; - } - - return ( -
- {children} -
- ); -} \ No newline at end of file + const { value, selectedvalue, children } = props; + + if (value !== selectedvalue) { + return null; + } + + return ( +
+ {children} +
+ ); +} + diff --git a/v2/src/components/question/index.tsx b/v2/src/components/question/index.tsx index 20a6b90e2..6d5b23c7b 100644 --- a/v2/src/components/question/index.tsx +++ b/v2/src/components/question/index.tsx @@ -1,126 +1,96 @@ -import React, { Children, PropsWithChildren, useState } from "react"; +import React, { + createContext, + Children, + PropsWithChildren, + useState, + useContext, + useCallback, + useEffect, +} from "react"; import "./question.css"; -export function Question(props: PropsWithChildren<{ - question: string | (() => JSX.Element), - persistentId?: string -}>) { - const [selectedAnsTitle, setSelectedAnsTitle] = useState(undefined); - - let onQuestionAnswered = React.useCallback((answer: string) => { - setSelectedAnsTitle(answer); - - if (props.persistentId !== undefined) { - localStorage.setItem("question-comp-" + props.persistentId, answer); - window.dispatchEvent(new Event("question-comp-" + props.persistentId)); - } - }, [setSelectedAnsTitle, props.persistentId]) +type QuestionContextType = { + answer?: string; + setAnswer: (answer: string) => void; +}; - React.useEffect(() => { - const onEventReceived = () => { - if(props.persistentId !== undefined){ - let answer = window.localStorage.getItem("question-comp-" + props.persistentId); +const QuestionContext = createContext( + {} as QuestionContextType, +); - if(answer !== null){ - setSelectedAnsTitle(answer) - } - } - } +const LOCAL_STORAGE_KEY_PREFIX = "supertokens-question-answer:"; - if(props.persistentId !== undefined){ - window.addEventListener("question-comp-" + props.persistentId, onEventReceived) - onEventReceived() // for initial loading - } +export function Question( + props: PropsWithChildren<{ + question: string; + defaultAnswer?: string; + }>, +) { + const { defaultAnswer, question, children } = props; + const [selectedAnswer, setSelectedAnswer] = useState( + defaultAnswer, + ); - return () => { - if (props.persistentId !== undefined) { - window.removeEventListener("question-comp-" + props.persistentId, onEventReceived) - } - } - }, [props.persistentId, setSelectedAnsTitle]) + const onSelectAnswer = useCallback((answer: string) => { + setSelectedAnswer(answer); + localStorage.setItem(`${LOCAL_STORAGE_KEY_PREFIX}:${question}`, answer); + }, []); - let resubmitInfoClicked = (event: any) => { - event.preventDefault(); - setSelectedAnsTitle(undefined); - }; + useEffect(() => { + const storedAnswer = localStorage.getItem( + `${LOCAL_STORAGE_KEY_PREFIX}:${question}`, + ); + if (storedAnswer !== null) { + setSelectedAnswer(storedAnswer); + } + }, []); - if (selectedAnsTitle === undefined) { - return ( -
-
- {typeof props.question === "string" ? props.question : props.question()} -
-
- {Children.map(props.children, (child: any, index: number) => { - return React.cloneElement(child, { - ...child.props, - onClick: () => onQuestionAnswered(child.props.title) - }); - })} -
-
- ); - } else { - let childrenComponent = null; - Children.forEach(props.children, (child: any) => { - if (child.props.title === selectedAnsTitle) { - childrenComponent = child.props.children; - } - }); - return ( - <> -
-
- Answer submitted -
-
-
- The content below is shown based on your answer. Resubmit answer? -
-
-
- {childrenComponent} - - ); + const selectedAnswerChildren = Children.map(props.children, (child) => { + if (!React.isValidElement(child)) return child; + const childTitle = child.props.title; + const childChildren = child.props.children; + if (childTitle === selectedAnswer) { + return childChildren; } + }); + + return ( + +
+
{question}
+
{children}
+
+ {selectedAnswerChildren} +
+ ); } type AnswerProps = { - title: string, - onClick?: () => void + title: string; + onClick?: () => void; }; export function Answer(props: PropsWithChildren) { + const { onClick: _onClick } = props; + const { answer, setAnswer } = useContext(QuestionContext); + const onClick = useCallback(() => { + if (_onClick !== undefined) { + _onClick(); + } + setAnswer(props.title); + }, []); - const [isMouseHover, setMouseHover] = useState(false); + return ( + + {props.title} + + ); +} - return ( - setMouseHover(true)} - onMouseLeave={() => setMouseHover(false)} - > - {props.title} - - ); -} \ No newline at end of file diff --git a/v2/src/components/question/question.css b/v2/src/components/question/question.css index bd66b17d9..82d7dce89 100644 --- a/v2/src/components/question/question.css +++ b/v2/src/components/question/question.css @@ -1,6 +1,6 @@ .question-box { width: 100%; - border-radius: .375rem; + border-radius: 0.375rem; border: 1px solid rgb(51, 51, 51); display: flex; flex-flow: column; @@ -18,7 +18,7 @@ } .question-box-answers { - padding: .75rem 1.5rem 0rem; + padding: 0.75rem 1.5rem 0rem; display: flex; flex-wrap: wrap; position: relative; @@ -32,12 +32,16 @@ font-size: 1.125rem; line-height: 22px; font-weight: bold; - color: rgb(255, 153, 51); + color: #fff; border: 1px solid rgb(77, 77, 77); border-radius: 6px; margin-bottom: 12px; } +.question-box-answer[data-is-selected="true"] { + color: rgb(255, 153, 51); +} + .question-box-answer:not(:last-of-type) { margin-right: 1.5rem; } @@ -61,4 +65,5 @@ margin-bottom: 20px; color: white; box-shadow: var(--container-box-shadow); -} \ No newline at end of file +} + diff --git a/v2/src/components/recipeBoxes/guides.json b/v2/src/components/recipeBoxes/guides.json index b57902cc5..c8b5e2183 100644 --- a/v2/src/components/recipeBoxes/guides.json +++ b/v2/src/components/recipeBoxes/guides.json @@ -3,31 +3,31 @@ { "title": "Email password with Social / Enterprise Login", "icon": "emailPassPlusSocial", - "path": "/docs/thirdpartyemailpassword/introduction", + "path": "/docs/thirdpartyemailpassword/quickstart/introduction", "img": "emailSocialSS" }, { "title": "Passwordless", "icon": "passwordless", - "path": "/docs/passwordless/introduction", + "path": "/docs/passwordless/quickstart/introduction", "img": "passwordlessSS" }, { "title": "Email password Login", "icon": "emailPass", - "path": "/docs/emailpassword/introduction", + "path": "/docs/emailpassword/quickstart/introduction", "img": "emailPassSS" }, { "title": "Social / Enterprise Login with Passwordless", "icon": "passLessSocial", - "path": "/docs/thirdpartypasswordless/introduction", + "path": "/docs/thirdpartypasswordless/quickstart/introduction", "img": "passwordlessSocialSS" }, { "title": "Social / Enterprise Login", "icon": "social", - "path": "/docs/thirdparty/introduction", + "path": "/docs/thirdparty/quickstart/introduction", "img": "socialSS" }, { diff --git a/v2/src/components/snippetConfigForm/formQuestion.tsx b/v2/src/components/snippetConfigForm/formQuestion.tsx index f8c08e6aa..4264aff35 100644 --- a/v2/src/components/snippetConfigForm/formQuestion.tsx +++ b/v2/src/components/snippetConfigForm/formQuestion.tsx @@ -1,100 +1,72 @@ -import React, { PropsWithChildren, useCallback, useEffect, useState } from "react"; +import React, { + PropsWithChildren, + useCallback, + useEffect, + useState, +} from "react"; import "../question/question.css"; type Option = { - title: string; - activeText?: string; - value: string; + title: string; + activeText?: string; + value: string; }; -export function Question(props: PropsWithChildren<{ +export function Question( + props: PropsWithChildren<{ question: string | (() => JSX.Element); options: Option[]; value?: string; onChange?: (title: string) => void; -}>) { - const [unselected, setUnselected] = useState(false); - - let resubmitInfoClicked = (event: any) => { - event.preventDefault(); - setUnselected(true); - }; - - if (unselected || props.value === undefined) { - return ( -
-
- {typeof props.question === "string" ? props.question : props.question()} -
-
- {props.options.map(opt => ( { - setUnselected(false); - if (props.onChange !== undefined) { - props.onChange(opt.value); - } - }} />))} -
-
- ); - } else { - // we always expect find to return a value, the object declaration is just to fix typescript - const selectedOption = props.options.find(opt => opt.value === props.value) || { - activeText: undefined, - title: "" - }; - return ( - <> -
-
- Answer submitted -
-
-
- {selectedOption.activeText !== undefined ? selectedOption.activeText : `You choose ${selectedOption.title}.`}{" "} - Resubmit answer? -
-
-
- - ); - } + }>, +) { + return ( + <> +
+
+ {typeof props.question === "string" + ? props.question + : props.question()} +
+
+ {props.options.map((opt) => ( + { + if (props.onChange !== undefined) { + props.onChange(opt.value); + } + }} + /> + ))} +
+
+ + ); } type AnswerProps = { - title: string, - onClick?: () => void + title: string; + isSelected: boolean; + onClick?: () => void; }; export function Answer(props: PropsWithChildren) { - const [isMouseHover, setMouseHover] = useState(false); + const [isMouseHover, setMouseHover] = useState(false); + + return ( + setMouseHover(true)} + onMouseLeave={() => setMouseHover(false)} + > + {props.title} + + ); +} - return ( - setMouseHover(true)} - onMouseLeave={() => setMouseHover(false)} - > - {props.title} - - ); -} \ No newline at end of file diff --git a/v2/src/components/snippetConfigForm/passwordlessQuestions.ts b/v2/src/components/snippetConfigForm/passwordlessQuestions.ts index 9aca7bda2..627f54f8b 100644 --- a/v2/src/components/snippetConfigForm/passwordlessQuestions.ts +++ b/v2/src/components/snippetConfigForm/passwordlessQuestions.ts @@ -4,18 +4,19 @@ export const passwordlessQuestions: Record> = { contactMethod: { id: "contactMethod", title: "How do you want to identify your users?", + default: "PHONE", options: [ { title: "Only phone number", activeText: "Your users will log in using a phone number.", value: "PHONE", variableMap: { - sendCB_Go: - `ContactMethodPhone: plessmodels.ContactMethodPhoneConfig{ + sendCB_Go: `ContactMethodPhone: plessmodels.ContactMethodPhoneConfig{ Enabled: true, },`, initialize_Python: "ContactPhoneOnlyConfig", - import_Python: "from supertokens_python.recipe.passwordless import ContactPhoneOnlyConfig" + import_Python: + "from supertokens_python.recipe.passwordless import ContactPhoneOnlyConfig", }, }, { @@ -23,24 +24,24 @@ export const passwordlessQuestions: Record> = { activeText: "Your users will log in using an email.", value: "EMAIL", variableMap: { - sendCB_Go: - `ContactMethodEmail: plessmodels.ContactMethodEmailConfig{ + sendCB_Go: `ContactMethodEmail: plessmodels.ContactMethodEmailConfig{ Enabled: true, },`, initialize_Python: "ContactEmailOnlyConfig", - import_Python: "from supertokens_python.recipe.passwordless import ContactEmailOnlyConfig" + import_Python: + "from supertokens_python.recipe.passwordless import ContactEmailOnlyConfig", }, }, { title: "Email or phone number", activeText: "Your users will log in using an email or a phone number.", variableMap: { - sendCB_Go: - `ContactMethodEmailOrPhone: plessmodels.ContactMethodEmailOrPhoneConfig{ + sendCB_Go: `ContactMethodEmailOrPhone: plessmodels.ContactMethodEmailOrPhoneConfig{ Enabled: true, },`, initialize_Python: "ContactEmailOrPhoneConfig", - import_Python: "from supertokens_python.recipe.passwordless import ContactEmailOrPhoneConfig" + import_Python: + "from supertokens_python.recipe.passwordless import ContactEmailOrPhoneConfig", }, value: "EMAIL_OR_PHONE", }, @@ -49,8 +50,13 @@ export const passwordlessQuestions: Record> = { flowType: { id: "flowType", title: "Which authentication type will you use?", + default: "USER_INPUT_CODE", options: [ - { title: "OTP", activeText: "Your users will authenticate by typing in an OTP.", value: "USER_INPUT_CODE" }, + { + title: "OTP", + activeText: "Your users will authenticate by typing in an OTP.", + value: "USER_INPUT_CODE", + }, { title: "Magic links", activeText: "Your users will authenticate by clicking a magic link.", @@ -58,7 +64,8 @@ export const passwordlessQuestions: Record> = { }, { title: "OTP and Magic link", - activeText: "Your users will authenticate by typing an OTP or clicking a magic link.", + activeText: + "Your users will authenticate by typing an OTP or clicking a magic link.", value: "USER_INPUT_CODE_AND_MAGIC_LINK", }, ], diff --git a/v2/src/plugins/copyDocsAndCodeTypeChecking.js b/v2/src/plugins/copyDocsAndCodeTypeChecking.js index 30c857038..6e48d11ad 100644 --- a/v2/src/plugins/copyDocsAndCodeTypeChecking.js +++ b/v2/src/plugins/copyDocsAndCodeTypeChecking.js @@ -28,7 +28,11 @@ async function checkFrontendSDKRelatedDocs(mdPath) { (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")) { + if ( + !mdPath.includes("pre-built-ui") && + !mdPath.includes("introduction") && + !mdPath.includes("quickstart") + ) { // 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( diff --git a/v2/src/theme/NavbarItem/recipeSelector.js b/v2/src/theme/NavbarItem/recipeSelector.js index c3bb84516..3600a5da6 100644 --- a/v2/src/theme/NavbarItem/recipeSelector.js +++ b/v2/src/theme/NavbarItem/recipeSelector.js @@ -91,7 +91,7 @@ export default function RecipeSelector(props) { active: activeSelector("thirdpartyemailpassword"), })} > - + Email password + Social / Enterprise Login @@ -100,14 +100,16 @@ export default function RecipeSelector(props) { active: activeSelector("passwordless"), })} > - Passwordless + + Passwordless +
  • - + Email password Login
  • @@ -116,7 +118,7 @@ export default function RecipeSelector(props) { active: activeSelector("thirdpartypasswordless"), })} > - + Social / Enterprise Login + Passwordless @@ -125,7 +127,7 @@ export default function RecipeSelector(props) { active: activeSelector("thirdparty"), })} > - + Social / Enterprise Login diff --git a/v2/src/theme/TabItem/index.js b/v2/src/theme/TabItem/index.js index aa61f477f..3465be1f1 100644 --- a/v2/src/theme/TabItem/index.js +++ b/v2/src/theme/TabItem/index.js @@ -4,7 +4,7 @@ * 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 React from "react"; function TabItem(props) { let { children, hidden, className } = props; @@ -14,7 +14,8 @@ function TabItem(props) { {...{ hidden, className, - }}> + }} + > {children} ); diff --git a/v2/src/theme/Tabs/index.js b/v2/src/theme/Tabs/index.js index 30d4a4906..45e388d40 100644 --- a/v2/src/theme/Tabs/index.js +++ b/v2/src/theme/Tabs/index.js @@ -4,10 +4,10 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ -import React, { useState, cloneElement, Children } from 'react'; -import useUserPreferencesContext from '@theme/hooks/useUserPreferencesContext'; -import clsx from 'clsx'; -import styles from './styles.module.css'; +import React, { useState, cloneElement, Children } from "react"; +import useUserPreferencesContext from "@theme/hooks/useUserPreferencesContext"; +import clsx from "clsx"; +import styles from "./styles.module.css"; function isInViewport(element) { const { top, left, bottom, right } = element.getBoundingClientRect(); @@ -48,8 +48,8 @@ function Tabs(props) { if (groupId != null) { setTabGroupChoices(groupId, selectedTabValue); - if (typeof window !== 'undefined') { - window.dispatchEvent(new Event('docs-tab-change')); + if (typeof window !== "undefined") { + window.dispatchEvent(new Event("docs-tab-change")); } setTimeout(() => { if (isInViewport(selectedTab)) { @@ -57,8 +57,8 @@ function Tabs(props) { } selectedTab.scrollIntoView({ - block: 'center', - behavior: 'smooth', + block: "center", + behavior: "smooth", }); selectedTab.classList.add(styles.tabItemActive); setTimeout( @@ -94,12 +94,9 @@ function Tabs(props) { return (
      + )} + > {values.map(({ value, label }) => (
    • tabRefs.push(tabControl)} onKeyDown={handleKeydown} onFocus={handleTabChange} - onClick={handleTabChange}> + onClick={handleTabChange} + > {label}
    • ))} @@ -144,7 +147,7 @@ function Tabs(props) { (tabItem) => tabItem.props.value === selectedValue, )[0], { - className: 'margin-vert--md', + className: "margin-vert--md", }, ) ) : ( diff --git a/v2/thirdparty/add-multiple-clients-for-the-same-provider.mdx b/v2/thirdparty/add-multiple-clients-for-the-same-provider.mdx new file mode 100644 index 000000000..960735447 --- /dev/null +++ b/v2/thirdparty/add-multiple-clients-for-the-same-provider.mdx @@ -0,0 +1,209 @@ +--- +id: add-multiple-clients-for-the-same-provider +title: Add Multiple Clients for the Same Provider +hide_title: true +show_ui_switcher: false +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" +import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" +import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; +import AppInfoForm from "/src/components/appInfoForm" + +# Add Multiple Clients for the Same Provider + +If you have social/SSO login for your web and mobile app, then you might need to setup different Client ID/Secret for the same provider on the backend. +For example, in case of Apple login, Apple gives you different client IDs for iOS login vs web & Android login (same client ID for web and Android). + +In order to get this to work, you would need to add additional clients to the Apple.init on the backend. Each client would need to be uniquely identified and this is done using the `clientType` string. For example, you can add one `clientType` for `web-and-android` and one for `ios`. + + + + +```tsx +import { ProviderInput } from "supertokens-node/recipe/thirdparty/types"; + +let providers: ProviderInput[] = [ + { + config: { + thirdPartyId: "apple", + clients: [{ + clientType: "web-and-android", + clientId: "...", + additionalConfig: { + "keyId": "...", + "privateKey": "...", + "teamId": "...", + } + }, { + clientType: "ios", + clientId: "...", + additionalConfig: { + "keyId": "...", + "privateKey": "...", + "teamId": "...", + } + }] + } + } +] +``` + + + + +```go +import ( + "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" +) + +func main() { + _ = []tpmodels.ProviderInput{{ + Config: tpmodels.ProviderConfig{ + ThirdPartyId: "apple", + Clients: []tpmodels.ProviderClientConfig{ + { + ClientType: "web-and-android", + ClientID: "...", + AdditionalConfig: map[string]interface{}{ + "keyId": "...", + "privateKey": "...", + "teamId": "...", + }, + }, + { + ClientType: "ios", + ClientID: "...", + AdditionalConfig: map[string]interface{}{ + "keyId": "...", + "privateKey": "...", + "teamId": "...", + }, + }, + }, + }, + }} +} +``` + + + + +```python +from supertokens_python.recipe.thirdparty.provider import ProviderInput, ProviderConfig, ProviderClientConfig + +providers = [ + ProviderInput( + config=ProviderConfig( + third_party_id="apple", + clients=[ + ProviderClientConfig( + client_type="web-and-android", + client_id="...", + additional_config={ + "keyId": "...", + "privateKey": "...", + "teamId": "...", + }, + ), + ProviderClientConfig( + client_type="ios", + client_id="...", + additional_config={ + "keyId": "...", + "privateKey": "...", + "teamId": "...", + }, + ), + ], + ), + ), +] +``` + + + + +For the frontend, you would need to use the right `clientType` as shown below: + + + + + + + + + +We pass in the `clientType` during the init call. + +```tsx +import SuperTokens from 'supertokens-web-js'; + +SuperTokens.init({ + appInfo: { + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}", + appName: "...", + }, + // highlight-next-line + clientType: "web-and-android", + recipeList: [/*...*/], +}); +``` + +If you are using our pre built UI SDK (supertokens-auth-react) as well, you can provide the `clientType` config to it as follows: + +```tsx +import SuperTokens from 'supertokens-auth-react'; + +SuperTokens.init({ + appInfo: { + apiDomain: "^{form_apiDomain}", + websiteDomain: "^{form_websiteDomain}", + apiBasePath: "^{form_apiBasePath}", + appName: "...", + }, + // highlight-next-line + clientType: "web-and-android", + recipeList: [/*...*/], +}); +``` + + + + + + + + +We pass in the `clientType` during the init call. + +```tsx +import supertokens from "supertokens-web-js-script"; +supertokens.init({ + appInfo: { + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}", + appName: "...", + }, + // highlight-next-line + clientType: "web-and-android", + recipeList: [/*...*/], +}); +``` + + + + + + + + + + +When making calls to the APIs from your mobile app, the request body also takes a `clientType` prop as seen in the above API calls. + + + diff --git a/v2/thirdparty/common-customizations/multi-tenancy/new-tenant-config.mdx b/v2/thirdparty/common-customizations/multi-tenancy/new-tenant-config.mdx index fa3e82cae..cf0a12065 100644 --- a/v2/thirdparty/common-customizations/multi-tenancy/new-tenant-config.mdx +++ b/v2/thirdparty/common-customizations/multi-tenancy/new-tenant-config.mdx @@ -25,160 +25,6 @@ The configuration mapped to each tenant contains information about which login m - - - - - -```tsx -import Multiteancy from "supertokens-node/recipe/multitenancy"; - -async function createTenant() { - let resp = await Multiteancy.createOrUpdateTenant("customer1", { - firstFactors: ["thirdparty"] - }); - - if (resp.createdNew) { - // new tenant was created - } else { - // existing tenant's config was modified. - } -} -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/multitenancy" - "github.com/supertokens/supertokens-golang/recipe/multitenancy/multitenancymodels" -) - -func main() { - tenantId := "customer1" - thirdPartyEnabled := true - - // highlight-start - resp, err := multitenancy.CreateOrUpdateTenant(tenantId, multitenancymodels.TenantConfig{ - ThirdPartyEnabled: &thirdPartyEnabled, - }) - // highlight-end - - if err != nil { - // handle error - } - if resp.OK.CreatedNew { - // new tenant was created - } else { - // existing tenant's config was modified. - } -} -``` - - - - - - - -```python -from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate - -async def some_func(): - result = await create_or_update_tenant( - "public", - TenantConfigCreateOrUpdate( - first_factors=["thirdparty"], - ), - ) - - if result.status != "OK": - print("handle error") - elif result.created_new: - print("new tenant was created") - else: - print("existing tenant's config was modified.") -``` - - - - - -```python -from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate - -def some_func(): - result = create_or_update_tenant( - "public", - TenantConfigCreateOrUpdate( - first_factors=["thirdparty"], - ), - ) - - if result.status != "OK": - print("handle error") - elif result.created_new: - print("new tenant was created") - else: - print("existing tenant's config was modified.") -``` - - - - - - - - - - - - - - -```bash -curl --location --request PUT '^{coreInjector_uri_without_quotes}/recipe/multitenancy/tenant/v2' \ ---header 'api-key: ^{coreInjector_api_key_without_quotes}' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "tenantId": "customer1", - "firstFactors": ["thirdparty"] -}' -``` - - - - - -```bash -curl --location --request PUT '^{coreInjector_uri_without_quotes}/recipe/multitenancy/tenant' \ ---header 'api-key: ^{coreInjector_api_key_without_quotes}' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "tenantId": "customer1", - "thirdPartyEnabled": true -}' -``` - - - - - - - - - - - -Create Tenant - - - - - ## Step 2: Configure the third party providers for the tenant @@ -190,174 +36,6 @@ Once again, you can add / modify this config dynamically using our backend SDK o - - - - - -```tsx -import Multiteancy from "supertokens-node/recipe/multitenancy"; - -async function createTenant() { - let resp = await Multiteancy.createOrUpdateThirdPartyConfig("customer1", { - thirdPartyId: "active-directory", - name: "Active Directory", - clients: [{ - clientId: "...", - clientSecret: "...", - }], - oidcDiscoveryEndpoint: "https://login.microsoftonline.com//v2.0/.well-known/openid-configuration", - }); - - if (resp.createdNew) { - // Active Directory added to customer1 - } else { - // Existing active directory config overwritten for customer1 - } -} -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/multitenancy" - "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" -) - -func main() { - tenantId := "customer1" - - // highlight-start - resp, err := multitenancy.CreateOrUpdateThirdPartyConfig(tenantId, tpmodels.ProviderConfig{ - ThirdPartyId: "active-directory", - Name: "Active Directory", - Clients: []tpmodels.ProviderClientConfig{ - { - ClientID: "...", - ClientSecret: "...", - }, - }, - OIDCDiscoveryEndpoint: "https://login.microsoftonline.com//v2.0/.well-known/openid-configuration", - }, nil, nil) - // highlight-end - - if err != nil { - // handle error - } - if resp.OK.CreatedNew { - // Active Directory added to customer1 - } else { - // Existing active directory config overwritten for customer1 - } -} -``` - - - - - - - -```python -from supertokens_python.recipe.multitenancy.asyncio import create_or_update_third_party_config -from supertokens_python.recipe.thirdparty.provider import ProviderConfig, ProviderClientConfig - -async def update_tenant(): - result = await create_or_update_third_party_config( - "customer1", - config=ProviderConfig( - third_party_id="active-directory", - name="Active Directory", - clients=[ - ProviderClientConfig( - client_id="...", - client_secret="...", - ) - ], - oidc_discovery_endpoint="https://login.microsoftonline.com//v2.0/.well-known/openid-configuration", - ), - ) - - if result.status != "OK": - print("Error adding active directory to tenant") - elif result.created_new: - print("Active directory was added to the tenant") - else: - print("Existing tenant's active directory config was modified") -``` - - - - - -```python -from supertokens_python.recipe.multitenancy.syncio import create_or_update_third_party_config -from supertokens_python.recipe.thirdparty.provider import ProviderConfig, ProviderClientConfig - -result = create_or_update_third_party_config( - "customer1", - config=ProviderConfig( - third_party_id="active-directory", - name="Active Directory", - clients=[ - ProviderClientConfig( - client_id="...", - client_secret="...", - ) - ] - ), - ) - -if result.status != "OK": - print("Error creating or updating tenant") -elif result.created_new: - print("New tenant was created") -else: - print("Existing tenant's config was modified") -``` - - - - - - - - - - -```bash -curl --location --request PUT '^{coreInjector_uri_without_quotes}//recipe/multitenancy/config/thirdparty' \ ---header 'api-key: ^{coreInjector_api_key_without_quotes}' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "config": { - "thirdPartyId": "active-directory", - "name": "Active Directory", - "clients": [ - { - "clientId": "...", - "clientSecret": "..." - } - ], - "oidcDiscoveryEndpoint": "https://login.microsoftonline.com//v2.0/.well-known/openid-configuration" - } -}' -``` - - - - - - - -Create Third Party Provider - - - - - - The above code snippet shows how you can add an Active directory login to your tenant. The `clientId`, `clientSecret` and `directoryId` will be provided to you by your tenant. diff --git a/v2/thirdparty/common-customizations/multi-tenancy/overview.mdx b/v2/thirdparty/common-customizations/multi-tenancy/overview.mdx index 6a015a043..d39b5732f 100644 --- a/v2/thirdparty/common-customizations/multi-tenancy/overview.mdx +++ b/v2/thirdparty/common-customizations/multi-tenancy/overview.mdx @@ -11,22 +11,6 @@ import TabItem from '@theme/TabItem'; - - - -# Multitenant and Enterprise SSO login - -Multitenant login is a feature that lets you customize the login experience for each of your customers. For example, a customer `customer1` hosted on `customer1.yourdomain.com` can have login with `Active Directory` and `Google`, and another customer `customer2` hosted on `customer2.yourdomain.com` can have login with `Okta` and `Facebook`. - -This is also the page that you should see if you want to implement sign in with: -- Okta (`thirdPartyId: "okta"`) -- SAML (`thirdPartyId: "boxy-saml"`) -- Active Directory (`thirdPartyId: "active-directory"`) -- Google Workspaces (`thirdPartyId: "google-workspaces"`) -- GitLab (`thirdPartyId: "gitlab"`) -- Bitbucket (`thirdPartyId: "bitbucket"`) -- Or any other workforce IdP - diff --git a/v2/thirdparty/common-customizations/sessions/protecting-frontend-routes.mdx b/v2/thirdparty/common-customizations/sessions/protecting-frontend-routes.mdx index 9e5719247..7a0f1c394 100644 --- a/v2/thirdparty/common-customizations/sessions/protecting-frontend-routes.mdx +++ b/v2/thirdparty/common-customizations/sessions/protecting-frontend-routes.mdx @@ -291,133 +291,6 @@ async function shouldLoadRoute(): Promise { - - - - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```tsx -import Session from 'supertokens-web-js/recipe/session'; - -async function doesSessionExist() { - if (await Session.doesSessionExist()) { - // user is logged in - } else { - // user has not logged in yet - } -} -``` - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```tsx -import Session from 'supertokens-web-js-script/recipe/session'; -async function doesSessionExist() { - if (await Session.doesSessionExist()) { - // user is logged in - } else { - // user has not logged in yet - } -} -``` - - - - - - - - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```tsx -import SuperTokens from 'supertokens-react-native'; - -async function doesSessionExist() { - if (await SuperTokens.doesSessionExist()) { - // user is logged in - } else { - // user has not logged in yet - } -} -``` - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```kotlin -import android.app.Application -import com.supertokens.session.SuperTokens - -class MainApplication: Application() { - fun doesSessionExist() { - if (SuperTokens.doesSessionExist(this.applicationContext)) { - // user is logged in - } else { - // user has not logged in yet - } - } -} -``` - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```swift -import UIKit -import SuperTokensIOS - -fileprivate class ViewController: UIViewController { - func doesSessionExist() { - if SuperTokens.doesSessionExist() { - // User is logged in - } else { - // User is not logged in - } - } -} -``` - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```dart -import 'package:supertokens_flutter/supertokens.dart'; - -Future doesSessionExist() async { - return await SuperTokens.doesSessionExist(); -} -``` - - - - - - - - - ## Verifying the claims of a session diff --git a/v2/thirdparty/custom-ui/enable-email-verification.mdx b/v2/thirdparty/custom-ui/enable-email-verification.mdx index b56cdf984..4f3ade3f4 100644 --- a/v2/thirdparty/custom-ui/enable-email-verification.mdx +++ b/v2/thirdparty/custom-ui/enable-email-verification.mdx @@ -4,1238 +4,8 @@ title: Enable email verification hide_title: true --- -# Enable email verification +import Redirector from '/src/components/Redirector'; -:::important -For social / third party logins, the user's email is automatically marked as verified if the user had verified their email to the login provider. -::: - - - + -import CustomAdmonition from "/src/components/customAdmonition" -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"; - - -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 - - - - -```tsx -import SuperTokens from "supertokens-node"; -import EmailVerification from "supertokens-node/recipe/emailverification"; -import Session from "supertokens-node/recipe/session"; - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // highlight-start - EmailVerification.init({ - mode: "REQUIRED", // or "OPTIONAL" - }), - // highlight-end - Session.init(), - ], -}); -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/emailverification" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evmodels" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - supertokens.Init(supertokens.TypeInput{ - RecipeList: []supertokens.Recipe{ - // highlight-start - emailverification.Init(evmodels.TypeInput{ - Mode: evmodels.ModeRequired, // or evmodels.ModeOptional - }), - // highlight-end - session.Init(&sessmodels.TypeInput{}), - }, - }) -} -``` - - - - -```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import session -from supertokens_python.recipe import emailverification - -init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore - recipe_list=[ - # highlight-start - emailverification.init(mode='REQUIRED'), # or 'OPTIONAL' - # highlight-end - session.init() - ] -) -``` - - - - - - - - - - -import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" -import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" -import WebJsInjector from "/src/components/webJsInjector" - -## Step 2: Frontend setup - - - - - - - -```tsx -import SuperTokens from "supertokens-web-js"; -import EmailVerification from "supertokens-web-js/recipe/emailverification"; -import Session from "supertokens-web-js/recipe/session"; - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - }, - recipeList: [ - // highlight-start - EmailVerification.init(), - Session.init(), - ], -}); -``` - - - - -Add the following ` -``` - - - -Then call the `supertokensEmailVerification.init` function as shown below - -```tsx -import supertokens from "supertokens-web-js-script"; -import supertokensEmailVerification from "supertokens-web-js-script/recipe/emailverification"; -import supertokensSession from "supertokens-web-js-script/recipe/session"; -supertokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - }, - recipeList: [ - // highlight-start - supertokensEmailVerification.init(), - supertokensSession.init(), - ], -}); -``` - - - - - - - - -:::success -No specific action required here. -::: - - - - - - - - - - -## Step 3: Checking if the user's email is verified in your APIs - -

      If using REQUIRED mode

      - -On the backend, when you initialize the email verification recipe in this mode, the `verifySession` middleware automatically checks if the user's email is verified based on the contents of the session's payload. If the email is not verified, the `verifySession` middleware will return a `403` status code to the client. - -

      If using OPTIONAL mode

      - -In this mode, you need to check if the email is verified yourself in the APIs in which you want this constraint. The verification status should already be in the session's payload. - - - - - - -```tsx -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import express from "express"; -import { SessionRequest } from "supertokens-node/framework/express"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let app = express(); - -app.post( - "/update-blog", - verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - }), - async (req: SessionRequest, res) => { - // All validator checks have passed and the user has a verified email address - } -); -``` - - - -```tsx -import Hapi from "@hapi/hapi"; -import { verifySession } from "supertokens-node/recipe/session/framework/hapi"; -import {SessionRequest} from "supertokens-node/framework/hapi"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let server = Hapi.server({ port: 8000 }); - -server.route({ - path: "/update-blog", - method: "post", - options: { - pre: [ - { - method: verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - }), - }, - ], - }, - handler: async (req: SessionRequest, res) => { - // All validator checks have passed and the user has a verified email address - } -}) -``` - - - -```tsx -import Fastify from "fastify"; -import { verifySession } from "supertokens-node/recipe/session/framework/fastify"; -import { SessionRequest } from "supertokens-node/framework/fastify"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let fastify = Fastify(); - -fastify.post("/update-blog", { - preHandler: verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - }), -}, async (req: SessionRequest, res) => { - // All validator checks have passed and the user has a verified email address -}); -``` - - - - -```tsx -import { verifySession } from "supertokens-node/recipe/session/framework/awsLambda"; -import { SessionEvent } from "supertokens-node/framework/awsLambda"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -async function updateBlog(awsEvent: SessionEvent) { - // All validator checks have passed and the user has a verified email address -}; - -exports.handler = verifySession(updateBlog, { - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] -}); -``` - - - - -```tsx -import KoaRouter from "koa-router"; -import { verifySession } from "supertokens-node/recipe/session/framework/koa"; -import {SessionContext} from "supertokens-node/framework/koa"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let router = new KoaRouter(); - -router.post("/update-blog", verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - }), async (ctx: SessionContext, next) => { - // All validator checks have passed and the user has a verified email address -}); -``` - - - - -```tsx -import { inject, intercept } from "@loopback/core"; -import { RestBindings, MiddlewareContext, post, response } from "@loopback/rest"; -import { verifySession } from "supertokens-node/recipe/session/framework/loopback"; -import Session from "supertokens-node/recipe/session"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -class SetRole { - constructor(@inject(RestBindings.Http.CONTEXT) private ctx: MiddlewareContext) { } - @post("/update-blog") - @intercept(verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - })) - @response(200) - async handler() { - // All validator checks have passed and the user has a verified email address - } -} -``` - - - - -```tsx -import { superTokensNextWrapper } from 'supertokens-node/nextjs' -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import { SessionRequest } from "supertokens-node/framework/express"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -// highlight-start -export default async function setRole(req: SessionRequest, res: any) { - await superTokensNextWrapper( - async (next) => { - await verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - })(req, res, next); - }, - req, - res - ) - // All validator checks have passed and the user has a verified email address -} -``` - - - - -```tsx -import SuperTokens from "supertokens-node"; -import { NextResponse, NextRequest } from "next/server"; -import { withSession } from "supertokens-node/nextjs"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; -// @ts-ignore -import { backendConfig } from "@/app/config/backend"; - -SuperTokens.init(backendConfig()); - -export async function POST(request: NextRequest) { - return withSession(request, async (err, session) => { - if (err) { - return NextResponse.json(err, { status: 500 }); - } - // All validator checks have passed and the user has a verified email address - return NextResponse.json({ message: "Your email is verified!" }); - }, - { - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - } - ); -} -``` - - - - -```tsx -import { Controller, Post, UseGuards, Request, Response, Session } from "@nestjs/common"; -import { SessionContainer, SessionClaimValidator } from "supertokens-node/recipe/session"; -// @ts-ignore -import { AuthGuard } from './auth/auth.guard'; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -@Controller() -export class ExampleController { - @Post('example') - @UseGuards(new AuthGuard({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators: SessionClaimValidator[]) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - })) - async postExample(@Session() session: SessionContainer): Promise { - // All validator checks have passed and the user has a verified email address - return true; - } -} -``` - - - - - - - - -```go -import ( - "net/http" - - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - _ = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - session.VerifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }, exampleAPI).ServeHTTP(rw, r) - }) -} - -func exampleAPI(w http.ResponseWriter, r *http.Request) { - // TODO: session is verified and all validators have passed.. -} -``` - - - - -```go -import ( - "net/http" - - "github.com/gin-gonic/gin" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - router := gin.New() - - // Wrap the API handler in session.VerifySession - router.POST("/likecomment", verifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }), exampleAPI) -} - -// This is a function that wraps the supertokens verification function -// to work the gin -func verifySession(options *sessmodels.VerifySessionOptions) gin.HandlerFunc { - return func(c *gin.Context) { - session.VerifySession(options, func(rw http.ResponseWriter, r *http.Request) { - c.Request = c.Request.WithContext(r.Context()) - c.Next() - })(c.Writer, c.Request) - // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly - c.Abort() - } -} - -func exampleAPI(c *gin.Context) { - // TODO: session is verified and all claim validators pass. -} -``` - - - - -```go -import ( - "net/http" - - "github.com/go-chi/chi" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - r := chi.NewRouter() - - // Wrap the API handler in session.VerifySession - r.Post("/likecomment", session.VerifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }, exampleAPI)) -} - -func exampleAPI(w http.ResponseWriter, r *http.Request) { - // TODO: session is verified and all claim validators pass. -} -``` - - - - -```go -import ( - "net/http" - - "github.com/gorilla/mux" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - router := mux.NewRouter() - - // Wrap the API handler in session.VerifySession - router.HandleFunc("/likecomment", session.VerifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }, exampleAPI)).Methods(http.MethodPost) -} - -func exampleAPI(w http.ResponseWriter, r *http.Request) { - // TODO: session is verified and all claim validators pass. -} -``` - - - - - - - - - -```python -from supertokens_python.recipe.session.framework.fastapi import verify_session -from supertokens_python.recipe.emailverification import EmailVerificationClaim -from supertokens_python.recipe.session import SessionContainer -from fastapi import Depends - -@app.post('/like_comment') # type: ignore -async def like_comment(session: SessionContainer = Depends( - verify_session( - # highlight-start - # We add the EmailVerificationClaim's is_verified validator - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \ - [EmailVerificationClaim.validators.is_verified()] - # highlight-end - ) -)): - # All validator checks have passed and the user has a verified email address - pass -``` - - - - -```python -from supertokens_python.recipe.session.framework.flask import verify_session -from supertokens_python.recipe.emailverification import EmailVerificationClaim - -@app.route('/update-jwt', methods=['POST']) # type: ignore -@verify_session( - # highlight-start - # We add the EmailVerificationClaim's is_verified validator - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \ - [EmailVerificationClaim.validators.is_verified()] - # highlight-end -) -def like_comment(): - # All validator checks have passed and the user has a verified email address - pass -``` - - - - -```python -from supertokens_python.recipe.session.framework.django.asyncio import verify_session -from django.http import HttpRequest -from supertokens_python.recipe.emailverification import EmailVerificationClaim - -@verify_session( - # highlight-start - # We add the EmailVerificationClaim's is_verified validator - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \ - [EmailVerificationClaim.validators.is_verified()] - # highlight-end -) -async def like_comment(request: HttpRequest): - # All validator checks have passed and the user has a verified email address - pass -``` - - - - - - - -We add the SDK's `EmailVerificationClaim` validator to the `verifySession` middleware call as shown above, and that will only allow access if the email is verified, else it will return `403` to the frontend. - - - - - - - -import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" - -## Step 4: Protecting frontend routes - - - - - - - -```tsx -import Session from "supertokens-web-js/recipe/session"; -import { EmailVerificationClaim } from "supertokens-web-js/recipe/emailverification"; - -async function shouldLoadRoute(): Promise { - if (await Session.doesSessionExist()) { - // highlight-start - let validationErrors = await Session.validateClaims(); - - if (validationErrors.length === 0) { - // user has verified their email address - return true; - } else { - for (const err of validationErrors) { - if (err.id === EmailVerificationClaim.id) { - // email is not verified - } - } - } - // highlight-end - } - // a session does not exist, or email is not verified - return false -} -``` - - - - -```tsx -import supertokensSession from "supertokens-web-js-script/recipe/session"; -import supertokensEmailVerification from "supertokens-web-js-script/recipe/emailverification"; -async function shouldLoadRoute(): Promise { - if (await supertokensSession.doesSessionExist()) { - // highlight-start - let validationErrors = await supertokensSession.validateClaims(); - - if (validationErrors.length === 0) { - // user has verified their email address - return true; - } else { - for (const err of validationErrors) { - if (err.id === supertokensEmailVerification.EmailVerificationClaim.id) { - // email is not verified - } - } - } - // highlight-end - } - // a session does not exist, or email is not verified - return false -} -``` - - - - -In your protected routes, you need to first check if a session exists, and then call the `Session.validateClaims` function as shown above. This function inspects the session's contents and runs claim validators on them. If a claim validator fails, it will be reflected in the `validationErrors` variable. The `EmailVerificationClaim` validator will be automatically checked by this function since you have initialized the email verification recipe. - -
      Handling 403 responses on the frontend - - - -If your frontend queries a protected API on your backend and it fails with a 403, you can call the `validateClaims` function and loop through the errors to know which claim has failed: - -```tsx -import axios from "axios"; -import Session from "supertokens-web-js/recipe/session"; -import { EmailVerificationClaim, sendVerificationEmail } from "supertokens-web-js/recipe/emailverification"; - -async function callProtectedRoute() { - try { - let response = await axios.get("^{form_apiDomain}/protectedroute"); - } catch (error) { - // highlight-start - if (axios.isAxiosError(error) && error.response?.status === 403) { - let validationErrors = await Session.validateClaims(); - for (let err of validationErrors) { - if (err.id === EmailVerificationClaim.id) { - // email verification claim check failed - // We call the sendEmail function defined in the next section to send the verification email. - // await sendEmail(); - } else { - // some other claim check failed (from the global validators list) - } - } - // highlight-end - - } - } -} -``` - - - -
      - -
      - - - - - - - -```tsx -import SuperTokens from 'supertokens-react-native'; - -async function checkIfEmailIsVerified() { - if (await SuperTokens.doesSessionExist()) { - - // highlight-start - let isVerified: boolean = (await SuperTokens.getAccessTokenPayloadSecurely())["st-ev"].v; - - if (isVerified) { - // TODO.. - } else { - // TODO.. - } - // highlight-end - } -} -``` - - - - - -```kotlin -import android.app.Application -import com.supertokens.session.SuperTokens -import org.json.JSONObject - -class MainApplication: Application() { - fun checkIfEmailIsVerified() { - val accessTokenPayload: JSONObject = SuperTokens.getAccessTokenPayloadSecurely(this); - val isVerified: Boolean = (accessTokenPayload.get("st-ev") as JSONObject).get("v") as Boolean - if (isVerified) { - // TODO.. - } else { - // TODO.. - } - } -} -``` - - - - - -```swift -import UIKit -import SuperTokensIOS - -fileprivate class ViewController: UIViewController { - func checkIfEmailIsVerified() { - if let accessTokenPayload: [String: Any] = try? SuperTokens.getAccessTokenPayloadSecurely(), let emailVerificationObject: [String: Any] = accessTokenPayload["st-ev"] as? [String: Any], let isVerified: Bool = emailVerificationObject["v"] as? Bool { - if isVerified { - // Email is verified - } else { - // Email is not verified - } - } - } -} -``` - - - - - -```dart -import 'package:supertokens_flutter/supertokens.dart'; - -Future checkIfEmailIsVerified() async { - var accessTokenPayload = await SuperTokens.getAccessTokenPayloadSecurely(); - - if (accessTokenPayload.containsKey("st-ev")) { - Map emailVerificationObject = accessTokenPayload["st-ev"]; - - if (emailVerificationObject.containsKey("v")) { - bool isVerified = emailVerificationObject["v"]; - - if (isVerified) { - // Email is verified - } else { - // Email is not verified - } - } - } -} -``` - - - - - -
      Handling 403 responses on the frontend - -If your frontend queries a protected API on your backend and it fails with a 403, you can check the value of the `st-ev` claim in the access token payload. If it is set to false you can send the verification email - -
      - -
      -
      - -import AppInfoForm from "/src/components/appInfoForm" - -## Step 5: Sending the email verification email - -When the email verification validators fail, or post sign up, you want to redirect the user to a screen telling them that a verification email has been sent to them. On this screen, you should call the following API - - - - - - - -```tsx -import { sendVerificationEmail } from "supertokens-web-js/recipe/emailverification"; - -async function sendEmail() { - try { - let response = await sendVerificationEmail(); - if (response.status === "EMAIL_ALREADY_VERIFIED_ERROR") { - // This can happen if the info about email verification in the session was outdated. - // Redirect the user to the home page - window.location.assign("/home"); - } else { - // email was sent successfully. - window.alert("Please check your email and click the link in it") - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - -```tsx -import supertokensEmailVerification from "supertokens-web-js/recipe/emailverification"; - -async function sendEmail() { - try { - let response = await supertokensEmailVerification.sendVerificationEmail(); - if (response.status === "EMAIL_ALREADY_VERIFIED_ERROR") { - // This can happen if the info about email verification in the session was outdated. - // Redirect the user to the home page - window.location.assign("/home"); - } else { - // email was sent successfully. - window.alert("Please check your email and click the link in it") - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - - - - - - - -You should create a new screen on your app that asks the user to enter their email to which an email will be sent. This screen should ideally be linked to from the sign in form. - -Once the user has enters their email, you can call the following API to send an email verification email to that user: - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/user/email/verify/token' \ ---header 'Authorization: Bearer ...' -``` - - - -The response body from the API call has a `status` property in it: -- `status: "OK"`: An email was sent to the user successfully. -- `status: "EMAIL_ALREADY_VERIFIED_ERROR"`: This status can be returned if the info about email verification in the session was outdated. Redirect the user to the home page. -- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. - - - -You do not need to add the tenant ID to the path here because the backend fetches the tenantId of the user from the session token. - - - - - - -:::note -The API for sending an email verification email requires an active session. If you are using our frontend SDKs, then the session tokens should automatically get attached to the request. -::: - -### Changing the email verification link domain / path -By default, the email verification link will point to the `websiteDomain` that is configured on the backend, on the `/auth/verify-email` route (where `/auth` is the default value of `websiteBasePath`). - -If you want to change this to a different path, a different domain, or deep link it to your mobile / desktop app, then you can do so on the backend in the following way: - - - - -```tsx -import SuperTokens from "supertokens-node"; -import EmailVerification from "supertokens-node/recipe/emailverification"; - -SuperTokens.init({ - supertokens: { - connectionURI: "...", - }, - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "..." - }, - recipeList: [ - EmailVerification.init({ - mode: "OPTIONAL", - // highlight-start - emailDelivery: { - override: (originalImplementation) => { - return { - ...originalImplementation, - sendEmail(input) { - return originalImplementation.sendEmail({ - ...input, - emailVerifyLink: input.emailVerifyLink.replace( - // This is: `${websiteDomain}${websiteBasePath}/verify-email` - "http://localhost:3000/auth/verify-email", - "http://localhost:3000/your/path" - ) - } - ) - }, - } - } - } - // highlight-end - }) - ] -}); -``` - - - -```go -import ( - "strings" - - "github.com/supertokens/supertokens-golang/ingredients/emaildelivery" - "github.com/supertokens/supertokens-golang/recipe/emailverification" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - supertokens.Init(supertokens.TypeInput{ - RecipeList: []supertokens.Recipe{ - emailverification.Init(evmodels.TypeInput{ - Mode: evmodels.ModeOptional, - // highlight-start - EmailDelivery: &emaildelivery.TypeInput{ - Override: func(originalImplementation emaildelivery.EmailDeliveryInterface) emaildelivery.EmailDeliveryInterface { - ogSendEmail := *originalImplementation.SendEmail - - (*originalImplementation.SendEmail) = func(input emaildelivery.EmailType, userContext supertokens.UserContext) error { - // This is: `${websiteDomain}${websiteBasePath}/verify-email` - input.EmailVerification.EmailVerifyLink = strings.Replace( - input.EmailVerification.EmailVerifyLink, - "http://localhost:3000/auth/verify-email", - "http://localhost:3000/your/path", 1, - ) - return ogSendEmail(input, userContext) - } - return originalImplementation - }, - }, - // highlight-end - }), - }, - }) -} -``` - - - -```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import emailverification -from supertokens_python.ingredients.emaildelivery.types import EmailDeliveryConfig -from supertokens_python.recipe.emailverification.types import EmailDeliveryOverrideInput, EmailTemplateVars -from typing import Dict, Any - - -def custom_email_delivery(original_implementation: EmailDeliveryOverrideInput) -> EmailDeliveryOverrideInput: - original_send_email = original_implementation.send_email - - # highlight-start - async def send_email(template_vars: EmailTemplateVars, user_context: Dict[str, Any]) -> None: - - # This is: `${websiteDomain}${websiteBasePath}/verify-email` - template_vars.email_verify_link = template_vars.email_verify_link.replace( - "http://localhost:3000/auth/verify-email", "http://localhost:3000/your/path") - - return await original_send_email(template_vars, user_context) - # highlight-end - - original_implementation.send_email = send_email - return original_implementation - - -init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore - recipe_list=[ - emailverification.init( - mode="OPTIONAL", - # highlight-next-line - email_delivery=EmailDeliveryConfig(override=custom_email_delivery)) - ] -) -``` - - - - - - - -For a multi tenant setup, the input to the `sendEmail` function will also contain the `tenantId`. You can use this to determine the correct value to set for the websiteDomain in the generated link. - - - -## Step 6: Verifying the email post link clicked - -Once the user clicks the email verification link, and it opens your app, you can call the following function which will automatically extract the token and tenantId (if using a multi tenant setup) from the link and call the token verification API. - - - - - - - -```tsx -import { verifyEmail } from "supertokens-web-js/recipe/emailverification"; - -async function consumeVerificationCode() { - try { - let response = await verifyEmail(); - if (response.status === "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR") { - // This can happen if the verification code is expired or invalid. - // You should ask the user to retry - window.alert("Oops! Seems like the verification link expired. Please try again") - window.location.assign("/auth/verify-email") // back to the email sending screen. - } else { - // email was verified successfully. - window.location.assign("/home") - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - -```tsx -import supertokensEmailVerification from "supertokens-web-js/recipe/emailverification"; - -async function consumeVerificationCode() { - try { - let response = await supertokensEmailVerification.verifyEmail(); - if (response.status === "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR") { - // This can happen if the verification code is expired or invalid. - // You should ask the user to retry - window.alert("Oops! Seems like the verification link expired. Please try again") - window.location.assign("/auth/verify-email") // back to the email sending screen. - } else { - // email was verified successfully. - window.location.assign("/home") - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - - - - - - - -Once the user clicks the email verification link, and it opens as a deep link into your mobile app, you can extract the token from the link and call the verification API as shown below: - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/user/email/verify' \ ---header 'Content-Type: application/json; charset=utf-8' \ ---data-raw '{ - "method": "token", - "token": "ZTRiOTBjNz...jI5MTZlODkxw" -}' -``` - - - - -For a multi tenancy setup, the `` value can be fetched from `tenantId` query parameter from the email verification link. If it's not there in the link, you can use the value `"public"` (which is the default tenant). - - - - - - -The response body from the API call has a `status` property in it: -- `status: "OK"`: Email verification was successful. -- `status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR"`: This can happen if the verification code is expired or invalid. You should ask the user to retry. -- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. - - - - -:::caution -- This API doesn't require an active session to succeed. -- If you are calling the above API on page load, there is an edge case in which email clients might open the verification link in the email (for scanning purposes) and consume the token in the URL. This would lead to issues in which an attacker could sign up using someone else's email and end up with a veriifed status! - - To prevent this, on page load, you should check if a session exists, and if it does, only then call the above API. If a session does not exist, you should first show a button, which when clicked would call the above API (email clients won't automatically click on this button). The button text could be something like "Click here to verify your email". -::: - - - - - - - -## See also - -- [Post email verification action](../common-customizations/email-verification/handling-email-verification-success) -- [Change email verification link's lifetime](../common-customizations/email-verification/changing-token-lifetime) -- [Customise email template or email delivery method](../email-delivery/about) -- [Manually changing email verification status for a user](../common-customizations/email-verification/changing-email-verification-status) -- [Generating email verification links manually](../common-customizations/email-verification/generate-link-manually) -- 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) in `REQUIRED` mode. - - \ No newline at end of file diff --git a/v2/thirdparty/custom-ui/handling-session-tokens.mdx b/v2/thirdparty/custom-ui/handling-session-tokens.mdx index bdfa1a21f..6ff934e75 100644 --- a/v2/thirdparty/custom-ui/handling-session-tokens.mdx +++ b/v2/thirdparty/custom-ui/handling-session-tokens.mdx @@ -4,412 +4,7 @@ title: Handling session tokens hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -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"; -import AppInfoForm from "/src/components/appInfoForm" -import {Question, Answer}from "/src/components/question" + -# Handling session tokens - -There are two modes ways in which you can use sessions with SuperTokens: -- Using `httpOnly` cookies -- Authorization bearer token. - -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 - -### For Web - - - - - -:::success -No action required. -::: - -Our frontend SDK handles everything for you. You only need to make sure that you have called `supertokens.init` before making any network requests. - -Our SDK adds interceptors to `fetch` and `XHR` (used by `axios`) to save and add session tokens from and to the request. - -By default, our web SDKs use cookies to provide credentials. - - - - - -import NetworkInterceptors from "/session/reusableMD/networkInterceptors.mdx" - -### For React-Native -Our frontend SDK handles everything for you. You only need to make sure that you have added our network interceptors as shown below - - - -:::note -By default our mobile SDKs use a bearer token in the Authorization header to provide credentials. -::: - -### For Android - - - - -```kotlin -import android.app.Application -import com.supertokens.session.SuperTokens -import com.supertokens.session.SuperTokensHttpURLConnection -import com.supertokens.session.SuperTokensPersistentCookieStore -import java.net.URL -import java.net.HttpURLConnection - -class MainApplication: Application() { - override fun onCreate() { - super.onCreate() - // TODO: Make sure to call SuperTokens.init - } - - fun makeRequest() { - val url = URL("") - val connection = SuperTokensHttpURLConnection.newRequest(url, object: SuperTokensHttpURLConnection.PreConnectCallback { - override fun doAction(con: HttpURLConnection?) { - // TODO: Use `con` to set request method, headers etc - } - }) - - // Handle response using connection object, for example: - if (connection.responseCode == 200) { - // TODO: implement - } - } -} -``` - -:::note -When making network requests you do not need to call `HttpURLConnection.connect` because SuperTokens does this for you. -::: - - - - -```kotlin -import android.content.Context -import com.supertokens.session.SuperTokens -import com.supertokens.session.SuperTokensInterceptor -import okhttp3.OkHttpClient -import retrofit2.Retrofit - -class NetworkManager { - fun getClient(context: Context): OkHttpClient { - val clientBuilder = OkHttpClient.Builder() - clientBuilder.addInterceptor(SuperTokensInterceptor()) - // TODO: Make sure to call SuperTokens.init - - val client = clientBuilder.build() - - // REQUIRED FOR RETROFIT ONLY - val instance = Retrofit.Builder() - .baseUrl("") - .client(client) - .build() - - return client - } - - fun makeRequest(context: Context) { - val client = getClient(context) - // Use client to make requests normally - } -} -``` - - - - -:::note -By default our mobile SDKs use a bearer token in the Authorization header to provide credentials. -::: - -### For iOS - - - - - -

      Using URLSession.shared

      - -```swift -import Foundation -import SuperTokensIOS - -fileprivate class NetworkManager { - func setupSuperTokensInterceptor() { - URLProtocol.registerClass(SuperTokensURLProtocol.self) - } -} -``` - -

      Using a custom URLSession instance

      - -```swift -import Foundation -import SuperTokensIOS - -fileprivate class NetworkManager { - func setupSuperTokensInterceptor() { - let configuration = URLSessionConfiguration.default - configuration.protocolClasses = [SuperTokensURLProtocol.self] - let session = URLSession(configuration: configuration) - - // Use session when making network requests - } -} -``` - -
      - - - -```swift -import Foundation -import SuperTokensIOS -import Alamofire - -fileprivate class NetworkManager { - func setupSuperTokensInterceptor() { - let configuration = URLSessionConfiguration.af.default - configuration.protocolClasses = [SuperTokensURLProtocol.self] + (configuration.protocolClasses ?? []) - let session = Session(configuration: configuration) - - // Use session when making network requests - } -} -``` - - - -
      - -:::note -By default our mobile SDKs use a bearer token in the Authorization header to provide credentials. -::: - -### For Flutter - - - - -You can make requests as you normally would with `http`, the only difference is that you import the client from the supertokens package instead. - -```dart -// Import http from the SuperTokens package -import 'package:supertokens_flutter/http.dart' as http; - -Future makeRequest() async { - Uri uri = Uri.parse("http://localhost:3001/api"); - var response = await http.get(uri); - // handle response -} -``` - -

      Using a custom http client

      - -If you use a custom http client and want to use SuperTokens, you can simply provide the SDK with your client. All requests will continue to use your client along with the session logic that SuperTokens provides. - -```dart -// Import http from the SuperTokens package -import 'package:supertokens_flutter/http.dart' as http; - -Future makeRequest() async { - Uri uri = Uri.parse("http://localhost:3001/api"); - - // Initialise your custom client - var customClient = http.Client(); - // provide your custom client to SuperTokens - var httpClient = http.Client(client: customClient); - - var response = await httpClient.get(uri); - // handle response -} -``` - -
      - - -

      Add the SuperTokens interceptor

      - -Use the extension method provided by the SuperTokens SDK to enable interception on your Dio client. This allows the SuperTokens SDK to handle session tokens for you. - -```dart -import 'package:supertokens_flutter/dio.dart'; -import 'package:dio/dio.dart'; - -void setup() { - Dio dio = Dio(); // Create a Dio instance. - dio.addSupertokensInterceptor(); -} -``` - -

      Making network requests

      - -You can make requests as you normally would with `dio`. - -```dart -import 'package:supertokens_flutter/dio.dart'; -import 'package:dio/dio.dart'; - -void setup() { - Dio dio = Dio( - // Provide your config here - ); - dio.addSupertokensInterceptor(); - - var response = dio.get("http://localhost:3001/api"); - // handle response -} -``` - -
      -
      - -:::note -By default our mobile SDKs use a bearer token in the Authorization header to provide credentials. -::: - -## If not using our frontend SDK - -:::caution -We highly recommend using our frontend SDK to handle session token management. It will save you a lot of time. -::: - -In this case, you will need to manually handle the tokens and session refreshing, and decide if you are going to use header or cookie-based sessions. - -For browsers, we recommend cookies, while for mobile apps (or if you don't want to use the built-in cookie manager) you should use header-based sessions. - - - - - -### On login - -You should attach the `st-auth-mode` header to calls to the login API, but this header is safe to attach to all requests. In this case it should be set to "cookie". - -The login API will return the following headers: -- `Set-Cookie`: This will contain the `sAccessToken`, `sRefreshToken` cookies which will be `httpOnly` and will be automatically mananaged by the browser. For mobile apps, you will need to setup cookie handling yourself, use our SDK or use a header based authentication mode. -- `front-token` header: This contains information about the access token: - - The userID - - The expiry time of the access token - - The payload added by you in the access token. - - Here is the structure of the token: - ```tsx - let frontTokenFromRequestHeader = "..."; - let frontTokenDecoded = JSON.parse(decodeURIComponent(escape(atob(frontTokenFromRequestHeader)))); - console.log(frontTokenDecoded); - /* - { - ate: 1665226412455, // time in milliseconds for when the access token will expire, and then a refresh is required - uid: "....", // user ID - up: { - sub: "..", - iat: .., - ... // other access token payload - } - } - - */ - ``` - - This token is mainly used for cookie based auth because you don't have access to the actual access token on the frontend (for security reasons), but may want to read its payload (for example to know the user's role). This token itself is not signed and hence can't be used in place of the access token itself. You may want to save this token in localstorage or in frontend cookies (using `document.cookies`). - -- `anti-csrf` header (optional): By default it's not required, so it's not sent. But if this is sent, you should save this token as well for use when making requests. - -### Making network requests to protected APIs - -The `sAccessToken` will get attached to the request automatically by the browser. Other than that, you need to add the following headers to the request: -- `rid: "anti-csrf"` - this prevents against anti-CSRF requests. If your `apiDomain` and `websiteDomain` values are exactly the same, then this is not necessary. -- `anti-csrf` header (optional): If this was provided to you during login, then you need to add that token as the value of this header. -- You need to set the `credentials` header to `true` or `include`. This is achieved different based on the library you are using to make requests. - -An API call can potentially update the `sAccessToken` and `front-token` tokens, for example if you call the `mergeIntoAccessTokenPayload` function on the `session` object on the backend. This kind of update is reflected in the response headers for your API calls. The headers will contain new values for: -- `sAccessToken`: This will be as a new `Set-Cookie` header and will be managed by the browser automatically. -- `front-token`: This should be read and saved by you in the same way as it's being done during login. - -### Handling session refreshing -If any of your API calls return with a status code of `401`, it means that the access token has expired. This will require you to refresh the session before retrying the same API call. - -You can call the refresh API as follows: - - - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/session/refresh' \ ---header 'Cookie: sRefreshToken=...' -``` - -:::note -- You may also need to add the `anti-csrf` header to the request if that was provided to you during sign in. -- The cURL command above shows the `sRefreshToken` cookie as well, but this is added by the web browser automatically, so you don't need to add it explicitly. -::: - - - -The result of a session refresh will be either: -- Status code `200`: This implies a succesful refresh. The set of tokens returned here will be the same as when the user logs in, so you can handle them in the same way. -- Status code `401`: This means that the refresh token is invalid, or has been revoked. You must ask the user to relogin. Remember to clear the `front-token` that you saved on the frontend earlier. - - - - - -### On login - -You should attach the `st-auth-mode` header to calls to the login API, but this header is safe to attach to all requests. In this case it should be set to "header". - -The login API will return the following headers: -- `st-access-token`: This contains the current access token associated with the session. You should save this in your application (e.g., in frontend localstorage). -- `st-refresh-token`: This contains the current refresh token associated with the session. You should save this in your application (e.g., in frontend localstorage). - -### Making network requests to protected APIs - -You need to add the following headers to request: -- `authorization: Bearer {access-token}` -- You need to set the `credentials` to `true` or `include`. This is achieved different based on the library you are using to make requests. - -An API call can potentially update the `access-token`, for example if you call the `mergeIntoAccessTokenPayload` function on the `session` object on the backend. This kind of update is reflected in the response headers for your API calls. The headers will contain new values for `st-access-token` - -These should be read and saved by you in the same way as it's being done during login. - -### Handling session refreshing -If any of your API calls return with a status code of `401`, it means that the access token has expired. This will require you to refresh the session before retrying the same API call. - -You can call the refresh API as follows: - - - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/session/refresh' \ ---header 'authorization: Bearer {refresh-token}' -``` - - - -The result of a session refresh will be either: -- Status code `200`: This implies a succesful refresh. The set of tokens returned here will be the same as when the user logs in, so you can handle them in the same way. -- Status code `401`: This means that the refresh token is invalid, or has been revoked. You must ask the user to relogin. Remember to clear the `st-refresh-token` and `st-access-token` that you saved on the frontend earlier. - - - - diff --git a/v2/thirdparty/custom-ui/init/backend.mdx b/v2/thirdparty/custom-ui/init/backend.mdx index 9c0495654..577fc5a32 100644 --- a/v2/thirdparty/custom-ui/init/backend.mdx +++ b/v2/thirdparty/custom-ui/init/backend.mdx @@ -4,1134 +4,8 @@ title: "Step 2: Backend" hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import TabItem from '@theme/TabItem'; -import NodeJSFrameworkSubTabsServerless from "/src/components/tabs/NodeJSFrameworkSubTabsServerless" -import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs" -import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" -import AppInfoForm from "/src/components/appInfoForm" -import CoreInjector from "/src/components/coreInjector" -import {Question, Answer}from "/src/components/question" -import BackendSDKCasing from "/src/components/BackendSDKCasing" -# Backend Integration + -## Supported frameworks - -import TechStackSupport from "../../../community/reusableMD/supported-tech-stacks-backend.mdx" - - - -## 1) Install - - - - -```bash -npm i -s supertokens-node -``` - - - - -```bash -go get github.com/supertokens/supertokens-golang -``` - - - - -```bash -pip install supertokens-python -``` - - - - -## 2) Initialise SuperTokens - - - - - -Add the code below to your server's init file. - - - - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import ThirdParty from "supertokens-node/recipe/thirdparty"; - -supertokens.init({ - framework: "express", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - // @ts-ignore - ThirdParty.init({/*TODO: See next step*/}), - Session.init() // initializes session features - ] -}); -``` - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import ThirdParty from "supertokens-node/recipe/thirdparty"; - -supertokens.init({ - framework: "hapi", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - // @ts-ignore - ThirdParty.init({/*TODO: See next step*/}), - Session.init() // initializes session features - ] -}); -``` - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import ThirdParty from "supertokens-node/recipe/thirdparty"; - -supertokens.init({ - framework: "fastify", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - // @ts-ignore - ThirdParty.init({/*TODO: See next step*/}), - Session.init() // initializes session features - ] -}); -``` - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import ThirdParty from "supertokens-node/recipe/thirdparty"; - -supertokens.init({ - framework: "koa", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - // @ts-ignore - ThirdParty.init({/*TODO: See next step*/}), - Session.init() // initializes session features - ] -}); -``` - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import ThirdParty from "supertokens-node/recipe/thirdparty"; - -supertokens.init({ - framework: "loopback", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - // @ts-ignore - ThirdParty.init({/*TODO: See next step*/}), - Session.init() // initializes session features - ] -}); -``` - - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/thirdparty" - "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - apiBasePath := "^{form_apiBasePath}" - websiteBasePath := "^{form_websiteBasePath}" - err := supertokens.Init(supertokens.TypeInput{ - Supertokens: &supertokens.ConnectionInfo{ - ^{coreInjector_connection_uri_comment} - ConnectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}APIKey: ^{coreInjector_api_key}, - }, - AppInfo: supertokens.AppInfo{ - AppName: "^{form_appName}", - APIDomain: "^{form_apiDomain}", - WebsiteDomain: "^{form_websiteDomain}", - APIBasePath: &apiBasePath, - WebsiteBasePath: &websiteBasePath, - }, - RecipeList: []supertokens.Recipe{ - thirdparty.Init(&tpmodels.TypeInput{/*TODO: See next step*/}), - session.Init(nil), // initializes session features - }, - }) - - if err != nil { - panic(err.Error()) - } -} -``` - - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig -from supertokens_python.recipe import thirdparty, session - -init( - app_info=InputAppInfo( - app_name="^{form_appName}", - api_domain="^{form_apiDomain}", - website_domain="^{form_websiteDomain}", - api_base_path="^{form_apiBasePath}", - website_base_path="^{form_websiteBasePath}" - ), - supertokens_config=SupertokensConfig( - ^{coreInjector_connection_uri_comment_with_hash} - connection_uri=^{coreInjector_uri} - ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} - ), - framework='fastapi', - recipe_list=[ - session.init(), # initializes session features - thirdparty.init( - # TODO: See next step - ) # type: ignore - ], - mode='asgi' # use wsgi if you are running using gunicorn -) -``` - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig -from supertokens_python.recipe import thirdparty, session - -init( - app_info=InputAppInfo( - app_name="^{form_appName}", - api_domain="^{form_apiDomain}", - website_domain="^{form_websiteDomain}", - api_base_path="^{form_apiBasePath}", - website_base_path="^{form_websiteBasePath}" - ), - supertokens_config=SupertokensConfig( - ^{coreInjector_connection_uri_comment_with_hash} - connection_uri=^{coreInjector_uri} - ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} - ), - framework='flask', - recipe_list=[ - session.init(), # initializes session features - thirdparty.init( - # TODO: See next step - ) # type: ignore - ] -) -``` - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig -from supertokens_python.recipe import thirdparty, session - -init( - app_info=InputAppInfo( - app_name="^{form_appName}", - api_domain="^{form_apiDomain}", - website_domain="^{form_websiteDomain}", - api_base_path="^{form_apiBasePath}", - website_base_path="^{form_websiteBasePath}" - ), - supertokens_config=SupertokensConfig( - ^{coreInjector_connection_uri_comment_with_hash} - connection_uri=^{coreInjector_uri} - ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} - ), - framework='django', - recipe_list=[ - session.init(), # initializes session features - thirdparty.init( - # TODO: See next step - ) # type: ignore - ], - mode='asgi' # use wsgi if you are running django server in sync mode -) -``` - - - - - - - - - - - -## 3) Initialise Social login providers - - - -Populate the `providers` array with the third party auth providers you want. - - - - -```tsx -import SuperTokens from "supertokens-node"; -import ThirdParty from "supertokens-node/recipe/thirdparty"; - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - ThirdParty.init({ - //highlight-start - signInAndUpFeature: { - // We have provided you with development keys which you can use for testing. - // IMPORTANT: Please replace them with your own OAuth keys for production use. - providers: [{ - config: { - thirdPartyId: "google", - clients: [{ - clientId: "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", - clientSecret: "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" - }] - } - }, { - config: { - thirdPartyId: "github", - clients: [{ - clientId: "467101b197249757c71f", - clientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd" - }] - } - }, { - config: { - thirdPartyId: "apple", - clients: [{ - clientId: "4398792-io.supertokens.example.service", - additionalConfig: { - keyId: "7M48Y4RYDL", - privateKey: - "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----", - teamId: "YWQCXGJRJL", - } - }] - } - }], - } - //highlight-end - }), - // ... - ] -}); -``` - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/thirdparty" - "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" -) - -func main() { - // Inside supertokens.Init - thirdparty.Init(&tpmodels.TypeInput{ - // highlight-start - SignInAndUpFeature: tpmodels.TypeInputSignInAndUp{ - Providers: []tpmodels.ProviderInput{ - // We have provided you with development keys which you can use for testing. - // IMPORTANT: Please replace them with your own OAuth keys for production use. - { - Config: tpmodels.ProviderConfig{ - ThirdPartyId: "google", - Clients: []tpmodels.ProviderClientConfig{ - { - ClientID: "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", - ClientSecret: "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", - }, - }, - }, - }, - { - Config: tpmodels.ProviderConfig{ - ThirdPartyId: "github", - Clients: []tpmodels.ProviderClientConfig{ - { - ClientID: "467101b197249757c71f", - ClientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd", - }, - }, - }, - }, - { - Config: tpmodels.ProviderConfig{ - ThirdPartyId: "apple", - Clients: []tpmodels.ProviderClientConfig{ - { - ClientID: "4398792-io.supertokens.example.service", - AdditionalConfig: map[string]interface{}{ - "keyId": "7M48Y4RYDL", - "privateKey": "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----", - "teamId": "YWQCXGJRJL", - }, - }, - }, - }, - }, - }, - }, - // highlight-end - }) -} -``` - - - -```python -from supertokens_python.recipe.thirdparty.provider import ProviderInput, ProviderConfig, ProviderClientConfig -from supertokens_python.recipe import thirdparty - -# Inside init -thirdparty.init( - # highlight-start - sign_in_and_up_feature=thirdparty.SignInAndUpFeature(providers=[ - # We have provided you with development keys which you can use for testing. - # IMPORTANT: Please replace them with your own OAuth keys for production use. - ProviderInput( - config=ProviderConfig( - third_party_id="google", - clients=[ - ProviderClientConfig( - client_id="1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", - client_secret="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", - ), - ], - ), - ), - ProviderInput( - config=ProviderConfig( - third_party_id="github", - clients=[ - ProviderClientConfig( - client_id="467101b197249757c71f", - client_secret="e97051221f4b6426e8fe8d51486396703012f5bd", - ) - ], - ), - ), - ProviderInput( - config=ProviderConfig( - third_party_id="apple", - clients=[ - ProviderClientConfig( - client_id="io.supertokens.example.service", - additional_config={ - "keyId": "7M48Y4RYDL", - "privateKey": "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----", - "teamId": "YWQCXGJRJL" - }, - ), - ], - ), - ), - ]) - # highlight-end -) -``` - - - - -**When you want to generate your own keys**, please refer to the corresponding documentation to get your client ids and client secrets for each of the below providers: - -
      -Google - -- Generate your client ID and secret by following the [docs here](https://support.google.com/cloud/answer/6158849?hl=en) -- Set the authorisation callback URL to `^{form_websiteDomain}^{form_websiteBasePathForCallbacks}/callback/google` - -
      - -
      -Github - -- Generate your client ID and secret by following the [docs here](https://docs.github.com/en/developers/apps/creating-an-oauth-app) -- Set the authorisation callback URL to `^{form_websiteDomain}^{form_websiteBasePathForCallbacks}/callback/github` - -
      - -
      -Apple - -- Generate your client ID and secret by following [this article](https://medium.com/identity-beyond-borders/how-to-configure-sign-in-with-apple-77c61e336003) -- Set the authorisation callback URL to `^{form_apiDomain}^{form_apiBasePathForCallbacks}/callback/apple`. Note that Apple doesn't allow `localhost` in the URL. So if you are in dev mode, you can use the dev keys we have provided above. - -
      - -:::important -You can find the list of built in providers [here](../../common-customizations/sign-in-and-up/built-in-providers). To add a provider that is not listed, you can follow our guide on [setting up custom providers](../../common-customizations/sign-in-and-up/custom-providers). -::: - -
      - -## 4) Add the SuperTokens APIs & CORS setup - - - - - - - - -:::important -- Add the `middleware` BEFORE all your routes. -- Add the `cors` middleware BEFORE the SuperTokens middleware as shown below. -::: - -```tsx -import express from "express"; -import cors from "cors"; -import supertokens from "supertokens-node"; -import { middleware } from "supertokens-node/framework/express"; - -let app = express(); - -app.use(cors({ - // highlight-start - origin: "^{form_websiteDomain}", - allowedHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], - credentials: true, - // highlight-end -})); - -// IMPORTANT: CORS should be before the below line. -// highlight-next-line -app.use(middleware()); - -// ...your API routes -``` -This `middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): - -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. - - - - -Register the `plugin`. - -```tsx -import Hapi from "@hapi/hapi"; -import supertokens from "supertokens-node"; -import { plugin } from "supertokens-node/framework/hapi"; - -let server = Hapi.server({ - port: 8000, - routes: { - // highlight-start - cors: { - origin: ["^{form_websiteDomain}"], - additionalHeaders: [...supertokens.getAllCORSHeaders()], - credentials: true, - } - // highlight-end - } -}); - -(async () => { - // highlight-next-line - await server.register(plugin); - - await server.start(); -})(); - -// ...your API routes -``` -This `plugin` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)) as well take care of all the errors thrown by the Supertokens library: - -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. - - - - -Register the `plugin`. Also register [`@fastify/formbody`](https://github.com/fastify/fastify-formbody) plugin. - -```tsx -import cors from "@fastify/cors"; -import supertokens from "supertokens-node"; -import { plugin } from "supertokens-node/framework/fastify"; -import formDataPlugin from "@fastify/formbody"; - -import fastifyImport from "fastify"; - -let fastify = fastifyImport(); - -// ...other middlewares -// highlight-start -fastify.register(cors, { - origin: "^{form_websiteDomain}", - allowedHeaders: ['Content-Type', ...supertokens.getAllCORSHeaders()], - credentials: true, -}); -// highlight-end - -(async () => { - // highlight-next-line - await fastify.register(formDataPlugin); - // highlight-next-line - await fastify.register(plugin); - - await fastify.listen(8000); -})(); - -// ...your API routes -``` -This `plugin` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): - -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. - - - - -:::important -Add the `middleware` BEFORE all your routes. -::: - -```tsx -import Koa from "koa"; -import cors from '@koa/cors'; -import supertokens from "supertokens-node"; -import { middleware } from "supertokens-node/framework/koa"; - -let app = new Koa(); - -// ...other middlewares -app.use(cors({ - // highlight-start - origin: "^{form_websiteDomain}", - allowHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], - credentials: true, - // highlight-end -})); - -// highlight-next-line -app.use(middleware()); - -// ...your API routes -``` -This `middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): - -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. - - - - -:::important -Add the `middleware` BEFORE all your routes. -::: - -```tsx -import { RestApplication } from "@loopback/rest"; -import supertokens from "supertokens-node"; -import { middleware } from "supertokens-node/framework/loopback"; - -let app = new RestApplication({ - rest: { - cors: { - // highlight-start - origin: "^{form_websiteDomain}", - allowedHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], - credentials: true - // highlight-end - } - } -}); - -// highlight-next-line -app.middleware(middleware); - -// ...your API routes -``` -This `middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): - -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. - - - - - - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "net/http" - "strings" - - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - //highlight-start - http.ListenAndServe("SERVER ADDRESS", corsMiddleware( - supertokens.Middleware(http.HandlerFunc(func(rw http.ResponseWriter, - //highlight-end - r *http.Request) { - // TODO: Handle your APIs.. - - })))) -} - -func corsMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(response http.ResponseWriter, r *http.Request) { - response.Header().Set("Access-Control-Allow-Origin", "^{form_websiteDomain}") - response.Header().Set("Access-Control-Allow-Credentials", "true") - if r.Method == "OPTIONS" { - // we add content-type + other headers used by SuperTokens - response.Header().Set("Access-Control-Allow-Headers", - strings.Join(append([]string{"Content-Type"}, - //highlight-start - supertokens.GetAllCORSHeaders()...), ",")) - //highlight-end - response.Header().Set("Access-Control-Allow-Methods", "*") - response.Write([]byte("")) - } else { - next.ServeHTTP(response, r) - } - }) -} -``` - - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "net/http" - - "github.com/gin-contrib/cors" - "github.com/gin-gonic/gin" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - router := gin.New() - - // CORS - router.Use(cors.New(cors.Config{ - AllowOrigins: []string{"^{form_websiteDomain}"}, - AllowMethods: []string{"GET", "POST", "DELETE", "PUT", "OPTIONS"}, - AllowHeaders: append([]string{"content-type"}, - // highlight-next-line - supertokens.GetAllCORSHeaders()...), - AllowCredentials: true, - })) - - // Adding the SuperTokens middleware - // highlight-start - router.Use(func(c *gin.Context) { - supertokens.Middleware(http.HandlerFunc( - func(rw http.ResponseWriter, r *http.Request) { - c.Next() - })).ServeHTTP(c.Writer, c.Request) - // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly - c.Abort() - }) - // highlight-end - - // Add APIs and start server -} -``` - - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "github.com/go-chi/chi" - "github.com/go-chi/cors" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - r := chi.NewRouter() - - // CORS - r.Use(cors.Handler(cors.Options{ - AllowedOrigins: []string{"^{form_websiteDomain}"}, - AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, - AllowedHeaders: append([]string{"Content-Type"}, - //highlight-next-line - supertokens.GetAllCORSHeaders()...), - AllowCredentials: true, - })) - - // SuperTokens Middleware - //highlight-next-line - r.Use(supertokens.Middleware) - - // Add APIs and start server -} -``` - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "net/http" - - "github.com/gorilla/handlers" - "github.com/gorilla/mux" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - // Add APIs - - router := mux.NewRouter() - - // Adding handlers.CORS(options)(supertokens.Middleware(router))) - //highlight-start - http.ListenAndServe("SERVER ADDRESS", handlers.CORS( - handlers.AllowedHeaders(append([]string{"Content-Type"}, - supertokens.GetAllCORSHeaders()...)), - handlers.AllowedMethods([]string{"GET", "POST", "PUT", "HEAD", "OPTIONS"}), - handlers.AllowedOrigins([]string{"^{form_websiteDomain}"}), - handlers.AllowCredentials(), - )(supertokens.Middleware(router))) - //highlight-end -} -``` - - - -This `Middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): - -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. - - - - - - - -Use the `Middleware` (**BEFORE all your routes**) and the `get_all_cors_headers()` functions as shown below. - -```python -from supertokens_python import get_all_cors_headers -from fastapi import FastAPI -from starlette.middleware.cors import CORSMiddleware -from supertokens_python.framework.fastapi import get_middleware - -app = FastAPI() -# highlight-next-line -app.add_middleware(get_middleware()) - -# TODO: Add APIs - -app.add_middleware( - CORSMiddleware, - allow_origins=[ - "^{form_websiteDomain}" - ], - allow_credentials=True, - allow_methods=["GET", "PUT", "POST", "DELETE", "OPTIONS", "PATCH"], - # highlight-next-line - allow_headers=["Content-Type"] + get_all_cors_headers(), -) - -# TODO: start server -``` - - - - -- Use the `Middleware` (**BEFORE all your routes and after calling init function**) and the `get_all_cors_headers()` functions as shown below. -- Add a route to catch all paths and return a 404. This is needed because if we don't add this, then OPTIONS request for the APIs exposed by the `Middleware` will return a `404`. - -```python -from supertokens_python import get_all_cors_headers -from flask import Flask, abort -from flask_cors import CORS # type: ignore -from supertokens_python.framework.flask import Middleware - -app = Flask(__name__) -# highlight-next-line -Middleware(app) - -# TODO: Add APIs - -CORS( - app=app, - origins=[ - "^{form_websiteDomain}" - ], - supports_credentials=True, - # highlight-next-line - allow_headers=["Content-Type"] + get_all_cors_headers(), -) - -# This is required since if this is not there, then OPTIONS requests for -# the APIs exposed by the supertokens' Middleware will return a 404 -# highlight-start -@app.route('/', defaults={'u_path': ''}) # type: ignore -@app.route('/') # type: ignore -def catch_all(u_path: str): - abort(404) -# highlight-end - -# TODO: start server -``` - - - - -Use the `Middleware` and the `get_all_cors_headers()` functions as shown below in your `settings.py`. - -```python -from supertokens_python import get_all_cors_headers -from typing import List -from corsheaders.defaults import default_headers - -CORS_ORIGIN_WHITELIST = [ - "^{form_websiteDomain}" -] - -CORS_ALLOW_CREDENTIALS = True - -CORS_ALLOWED_ORIGINS = [ - "^{form_websiteDomain}" -] - -CORS_ALLOW_HEADERS: List[str] = list(default_headers) + [ - "Content-Type" - # highlight-next-line -] + get_all_cors_headers() - -INSTALLED_APPS = [ - 'corsheaders', - 'supertokens_python' -] - -MIDDLEWARE = [ - 'corsheaders.middleware.CorsMiddleware', - ..., - # highlight-next-line - 'supertokens_python.framework.django.django_middleware.middleware', -] -``` - - - - -This `Middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): - -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. - - - - - - -## 5) Add the SuperTokens error handler -Add the `errorHandler` **AFTER all your routes**, but **BEFORE your error handler** - - - - - - -```tsx -import express, { Request, Response, NextFunction } from "express"; -import { errorHandler } from "supertokens-node/framework/express"; - -let app = express(); -// ...your API routes - -// highlight-start -// Add this AFTER all your routes -app.use(errorHandler()) -// highlight-end - -// your own error handler -app.use((err: unknown, req: Request, res: Response, next: NextFunction) => { /* ... */ }); -``` - - - -No additional `errorHandler` is required. - - - - -Add the `errorHandler` **Before all your routes and plugin registration** - -```tsx -import Fastify from "fastify"; -import { errorHandler } from "supertokens-node/framework/fastify"; - -let fastify = Fastify(); - -// highlight-next-line -fastify.setErrorHandler(errorHandler()); - -// ...your API routes -``` - - - -No additional `errorHandler` is required. - - - - -No additional `errorHandler` is required. - - - - - - -:::info -You can skip this step -::: - - - - -:::info -You can skip this step -::: - - - - -## 6) Setup the SuperTokens core - -You need to now setup an instance of the SuperTokens core for your app (that your backend should connect to). You have two options: -- [Managed service](./core/saas-setup) -- Self hosted with your own database ([With Docker](./core/with-docker) or [Without Docker](./core/without-docker)) diff --git a/v2/thirdparty/custom-ui/init/core/managed-service.mdx b/v2/thirdparty/custom-ui/init/core/managed-service.mdx index 5dade6aca..726e4dc4a 100644 --- a/v2/thirdparty/custom-ui/init/core/managed-service.mdx +++ b/v2/thirdparty/custom-ui/init/core/managed-service.mdx @@ -4,95 +4,9 @@ title: Managed Service hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import TabItem from '@theme/TabItem'; -# Managed Service + -## Creating a development environment ✨ -- First, please [sign up](https://supertokens.com/auth) -- Select the auth method you want to use and follow the guided steps to integrate with our frontend and backend SDK if you have not done this already -Integration with SuperTokens SDKs -- Select a region and click the deploy button: -:::tip -You should select a region that is closest to your backend. -::: - -Deploying SuperTokens Core - -- After the deployment is complete the dashboard will look similar to this: -Deployed SuperTokens Core - -## Connecting the backend SDK with SuperTokens 🔌 -- Add the `connectionURI` and the `apiKey` shown on the dashboard to your code on the backend. - -SuperTokens managed service dashboard connectionURI and API key - - - - -```tsx -import supertokens from "supertokens-node"; - -supertokens.init({ - // highlight-start - supertokens: { - connectionURI: "", - apiKey: "" - }, - // highlight-end - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "..." - }, - recipeList: [] -}); -``` - - - - -```go -import "github.com/supertokens/supertokens-golang/supertokens" - -func main() { - supertokens.Init(supertokens.TypeInput{ - // highlight-start - Supertokens: &supertokens.ConnectionInfo{ - ConnectionURI: "", - APIKey: "", - }, - // highlight-end - }) -} - -``` - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig - -init( - app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), - # highlight-start - supertokens_config=SupertokensConfig( - connection_uri='', - api_key='' - ), - # highlight-end - framework='...', # type: ignore - recipe_list=[ - #... - ] -) -``` - - - diff --git a/v2/thirdparty/custom-ui/init/core/self-hosted-with-docker.mdx b/v2/thirdparty/custom-ui/init/core/self-hosted-with-docker.mdx index 1df643ac0..e31d7f6e6 100644 --- a/v2/thirdparty/custom-ui/init/core/self-hosted-with-docker.mdx +++ b/v2/thirdparty/custom-ui/init/core/self-hosted-with-docker.mdx @@ -4,268 +4,8 @@ title: With Docker hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import DatabaseTabs from "/src/components/tabs/DatabaseTabs" -import TabItem from '@theme/TabItem'; -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import DockerVersionProvider from "/src/components/dockerVersionProvider"; -# With Docker + -## Running the docker image 🚀 - - - - - - -```bash -docker run -p 3567:3567 -d registry.supertokens.io/supertokens/supertokens-mysql^{docker_version_mysql} -``` - -- To see all the env variables available, please see [the README file](https://github.com/supertokens/supertokens-docker-mysql/blob/master/README.md). -- The above command will start the container with an in-memory database. This means you **do not need to connect it to MySQL to test out SuperTokens**. -- When you are ready to connect it to your database, please visit the [Database setup section](../database-setup/mysql) - - - - - -```bash -docker run -p 3567:3567 -d registry.supertokens.io/supertokens/supertokens-postgresql^{docker_version_postgresql} -``` - -- To see all the env variables available, please see [the README file](https://github.com/supertokens/supertokens-docker-postgresql/blob/master/README.md). -- The above command will start the container with an in-memory database. This means you **do not need to connect it to PostgreSQL to test out SuperTokens**. -- When you are ready to connect it to your database, please visit the [Database setup section](../database-setup/postgresql) - - - - - -```bash -docker run -p 3567:3567 -d registry.supertokens.io/supertokens/supertokens-mongodb^{docker_version_mongodb} -``` - -- To see all the env variables available, please see [the README file](https://github.com/supertokens/supertokens-docker-mongodb/blob/master/README.md). -- The above command will start the container with an in-memory database. This means you **do not need to connect it to MongoDB to test out SuperTokens**. -- When you are ready to connect it to your database, please visit the [Database setup section](../database-setup/mongodb) - -:::caution -We do not offer login functionality with MongDB yet. We only offer session management. -::: - - - - - -## Testing that the service is running 🤞 -Open a browser and visit `http://localhost:3567/hello`. If you see a page that says `Hello` back, then the container was started successfully! - -If you are having issues with starting the docker image, please feel free to reach out to us [over email](mailto:team@supertokens.com) or [via Discord](https://supertokens.com/discord). - -:::tip -The `/hello` route checks whether the database connection is set up correctly and will only return a 200 status code if there is no issue. - -If you are using kubernetes or docker swarm, this endpoint is perfect for doing readiness and liveness probes. -::: - -## Connecting the backend SDK with SuperTokens 🔌 -- The default `port` for SuperTokens is `3567`. You can change this by binding a different port in the `docker run` command. For example, `docker run -p 8080:3567` will run SuperTokens on port `8080` on your machine. -- The connection info will go in the `supertokens` object in the `init` function on your backend: - - - - -```tsx -import supertokens from "supertokens-node"; - -supertokens.init({ - // highlight-start - supertokens: { - connectionURI: "http://localhost:3567", - apiKey: "someKey" // OR can be undefined - }, - // highlight-end - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "..." - }, - recipeList: [] -}); -``` - - - - -```go -import "github.com/supertokens/supertokens-golang/supertokens" - -func main() { - supertokens.Init(supertokens.TypeInput{ - // highlight-start - Supertokens: &supertokens.ConnectionInfo{ - ConnectionURI: "http://localhost:3567", - APIKey: "someKey", - }, - // highlight-end - }) -} - -``` - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig - -init( - app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), - # highlight-start - supertokens_config=SupertokensConfig( - connection_uri='http://localhost:3567', - api_key='someKey' - ), - # highlight-end - framework='...', # type: ignore - recipe_list=[ - #... - ] -) -``` - - - - -:::tip Security -There is no API key by default. Visit the "Auth flow customization" -> "SuperTokens core settings" -> "Adding API Keys" section to see how to add one. -::: - -## Docker compose file - - - - - - -```bash -version: '3' - -services: - db: - image: mysql:latest - environment: - MYSQL_ROOT_PASSWORD: root - MYSQL_USER: supertokens_user - MYSQL_PASSWORD: somePassword - MYSQL_DATABASE: supertokens - ports: - - 3306:3306 - networks: - - app_network - restart: unless-stopped - healthcheck: - test: [ "CMD", "mysqladmin", "ping", "-h", "localhost" ] - timeout: 20s - retries: 10 - - supertokens: - image: registry.supertokens.io/supertokens/supertokens-mysql^{docker_version_mysql} - depends_on: - db: - condition: service_healthy - ports: - - 3567:3567 - environment: - MYSQL_CONNECTION_URI: mysql://supertokens_user:somePassword@db:3306/supertokens - networks: - - app_network - restart: unless-stopped - healthcheck: - test: > - bash -c 'exec 3<>/dev/tcp/127.0.0.1/3567 && echo -e "GET /hello HTTP/1.1\r\nhost: 127.0.0.1:3567\r\nConnection: close\r\n\r\n" >&3 && cat <&3 | grep "Hello"' - interval: 10s - timeout: 5s - retries: 5 - -networks: - app_network: - driver: bridge -``` - - - - - -```bash -version: '3' - -services: - # Note: If you are assigning a custom name to your db service on the line below, make sure it does not contain underscores - db: - image: 'postgres:latest' - environment: - POSTGRES_USER: supertokens_user - POSTGRES_PASSWORD: somePassword - POSTGRES_DB: supertokens - ports: - - 5432:5432 - networks: - - app_network - restart: unless-stopped - healthcheck: - test: ['CMD', 'pg_isready', '-U', 'supertokens_user', '-d', 'supertokens'] - interval: 5s - timeout: 5s - retries: 5 - - supertokens: - image: registry.supertokens.io/supertokens/supertokens-postgresql^{docker_version_postgresql} - depends_on: - db: - condition: service_healthy - ports: - - 3567:3567 - environment: - POSTGRESQL_CONNECTION_URI: "postgresql://supertokens_user:somePassword@db:5432/supertokens" - networks: - - app_network - restart: unless-stopped - healthcheck: - test: > - bash -c 'exec 3<>/dev/tcp/127.0.0.1/3567 && echo -e "GET /hello HTTP/1.1\r\nhost: 127.0.0.1:3567\r\nConnection: close\r\n\r\n" >&3 && cat <&3 | grep "Hello"' - interval: 10s - timeout: 5s - retries: 5 - -networks: - app_network: - driver: bridge -``` - - - - - - -We are working on adding this section. - - - - - - -:::important -If you are running the backend process that integrates with our backend sdk as part of the docker compose file as well, make sure to use `http://supertokens:3567` as the connection uri instead of `http://localhost:3567`. -::: - - -## Helm charts for Kubernetes - -- For [MySQL image](https://github.com/supertokens/supertokens-docker-mysql/tree/master/helm-chart) - -- For [PostgreSQL image](https://github.com/supertokens/supertokens-docker-postgresql/tree/master/helm-chart) diff --git a/v2/thirdparty/custom-ui/init/core/self-hosted-without-docker.mdx b/v2/thirdparty/custom-ui/init/core/self-hosted-without-docker.mdx index 9a44a4e8c..7068a55cb 100644 --- a/v2/thirdparty/custom-ui/init/core/self-hosted-without-docker.mdx +++ b/v2/thirdparty/custom-ui/init/core/self-hosted-without-docker.mdx @@ -4,165 +4,9 @@ title: Without Docker hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import OSTabs from "/src/components/tabs/OSTabs"; -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -# Binary Installation + -## 1) Download SuperTokens -- Visit the [open source download page](https://supertokens.com/use-oss). -- Click on the "Binary" tab. -- Choose your database. -- Download the SuperTokens zip file for your OS. - -Once downloaded, extract the zip, and you will see a folder named `supertokens`. - -## 2) Install SuperTokens - - - - -```bash -# sudo is required so that the supertokens -# command can be added to your PATH variable. - -cd supertokens -sudo ./install -``` - - - - -```bash - -cd supertokens -./install - -``` -:::caution -You may get an error like `java cannot be opened because the developer cannot be verified`. To solve this, visit System Preferences > Security & Privacy > General Tab, and then click on the Allow button at the bottom. Then retry the command above. -::: - - - - - -```batch - -Rem run as an Administrator. This is required so that the supertokens -Rem command can be added to your PATH. - -cd supertokens -install.bat - -``` - - - -:::important -After installing, you can delete the downloaded folder as you no longer need it. - -Any changes to the the config will be done in the `config.yaml` file in the installation directory, the location of which is specified in the output of the `supertokens --help` command. -::: - -## 3) Start SuperTokens 🚀 -Running the following command will start the service. -```bash -supertokens start [--host=...] [--port=...] -``` -- The above command will start the container with an in-memory database. -- When you are ready to connect it to your database, please visit the [Database setup section](../database-setup/mysql) -- To see all available options please run `supertokens start --help` - - -## 4) Testing that the service is running 🤞 -Open a browser and visit `http://localhost:3567/hello`. If you see a page that says `Hello` back, then SuperTokens was started successfully! - -If you are having issues with starting the docker image, please feel free to reach out to us [over email](mailto:team@supertokens.com) or [via Discord](https://supertokens.com/discord). - - -## 5) Stopping SuperTokens 🛑 -```bash -supertokens stop -``` - -## Connecting the backend SDK with SuperTokens 🔌 -- The default `host` and `port` for SuperTokens is `localhost:3567`. You can change this by passing `--host` and `--port` options to the `start` command. -- The connection info will go in the `supertokens` object in the `init` function on your backend: - - - - - -```tsx -import supertokens from "supertokens-node"; - -supertokens.init({ - // highlight-start - supertokens: { - connectionURI: "http://localhost:3567", - apiKey: "someKey" // OR can be undefined - }, - // highlight-end - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "..." - }, - recipeList: [] -}); - -``` - - - - -```go -import "github.com/supertokens/supertokens-golang/supertokens" - -func main() { - supertokens.Init(supertokens.TypeInput{ - // highlight-start - Supertokens: &supertokens.ConnectionInfo{ - ConnectionURI: "http://localhost:3567", - APIKey: "someKey", - }, - // highlight-end - }) -} - -``` - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig - -init( - app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), - # highlight-start - supertokens_config=SupertokensConfig( - connection_uri='http://localhost:3567', - api_key='someKey' - ), - # highlight-end - framework='...', # type: ignore - recipe_list=[ - #... - ] -) -``` - - - - -:::tip Security -There is no API key by default. Visit the "Auth flow customization" -> "SuperTokens core settings" -> "Adding API Keys" section to see how to add one. -::: diff --git a/v2/thirdparty/custom-ui/init/database-setup/mysql.mdx b/v2/thirdparty/custom-ui/init/database-setup/mysql.mdx index eb37456b2..3597e3e64 100644 --- a/v2/thirdparty/custom-ui/init/database-setup/mysql.mdx +++ b/v2/thirdparty/custom-ui/init/database-setup/mysql.mdx @@ -4,533 +4,7 @@ title: If using MySQL hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import SelfHostedTabs from "/src/components/tabs/SelfHostedTabs" -import TabItem from '@theme/TabItem'; + -# MySQL setup - -:::important -This is needed only if you are running the SuperTokens core yourself. - -The minimum required version is **MySQL 5.7**. -::: - -:::tip blog -We also have [a blog post writeup](https://supertokens.com/blog/connect-supertokens-to-database) highlighting all the steps in more detail for different scenarios. -::: - -## 1) Create a database 🛠️ -```sql -CREATE DATABASE supertokens; -``` -You can skip this step if you want SuperTokens to write to your own database. In this case, you will need to provide your database's name as shown in the step below. - -## 2) Connect SuperTokens to your database 🔌 - - - - -:::caution -Host being `localhost` / `127.0.0.1` will not work in a docker image. Instead, please provide the database's local / public hostname or IP address. - -You also need to make the database listen on all the IP's of the local machine. -This can be done by editing the `mysqld.cnf` config file and setting the value of `bind-address` to `0.0.0.0`. -::: - -```bash - -docker run \ - -p 3567:3567 \ -//highlight-next-line - -e MYSQL_CONNECTION_URI="mysql://username:pass@host/dbName" \ - -d registry.supertokens.io/supertokens/supertokens-mysql - -# OR - -docker run \ - -p 3567:3567 \ -//highlight-start - -e MYSQL_USER="username" \ - -e MYSQL_PASSWORD="password" \ - -e MYSQL_HOST="host" \ - -e MYSQL_PORT="3306" \ - -e MYSQL_DATABASE_NAME="supertokens" \ -//highlight-end - -d registry.supertokens.io/supertokens/supertokens-mysql -``` - - - - - - - -```yaml -# You need to add the following to the config.yaml file. -# The file path can be found by running the "supertokens --help" command - -mysql_connection_uri: "mysql://username:pass@host/dbName" - -# OR - -mysql_user: "username" - -mysql_password: "password" - -mysql_host: "host" - -mysql_port: 3306 - -mysql_database_name: "supertokens" -``` - - - - -## 3) Create tables 👩‍💻👨‍💻 - -:::note -This happens automatically, unless you provide a MySQL user that doesn't have table creation permission. -::: - -```sql -CREATE TABLE `apps` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `created_at_time` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`) -); - -CREATE TABLE `tenants` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `created_at_time` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `tenant_configs` ( - `connection_uri_domain` varchar(256) NOT NULL DEFAULT '', - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `core_config` text, - `email_password_enabled` tinyint(1) DEFAULT NULL, - `passwordless_enabled` tinyint(1) DEFAULT NULL, - `third_party_enabled` tinyint(1) DEFAULT NULL, - `is_first_factors_null` tinyint(1) DEFAULT NULL, - PRIMARY KEY (`connection_uri_domain`,`app_id`,`tenant_id`) -); - -CREATE TABLE `tenant_thirdparty_providers` ( - `connection_uri_domain` varchar(256) NOT NULL DEFAULT '', - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `third_party_id` varchar(28) NOT NULL, - `name` varchar(64) DEFAULT NULL, - `authorization_endpoint` text, - `authorization_endpoint_query_params` text, - `token_endpoint` text, - `token_endpoint_body_params` text, - `user_info_endpoint` text, - `user_info_endpoint_query_params` text, - `user_info_endpoint_headers` text, - `jwks_uri` text, - `oidc_discovery_endpoint` text, - `require_email` tinyint(1) DEFAULT NULL, - `user_info_map_from_id_token_payload_user_id` varchar(64) DEFAULT NULL, - `user_info_map_from_id_token_payload_email` varchar(64) DEFAULT NULL, - `user_info_map_from_id_token_payload_email_verified` varchar(64) DEFAULT NULL, - `user_info_map_from_user_info_endpoint_user_id` varchar(64) DEFAULT NULL, - `user_info_map_from_user_info_endpoint_email` varchar(64) DEFAULT NULL, - `user_info_map_from_user_info_endpoint_email_verified` varchar(64) DEFAULT NULL, - PRIMARY KEY (`connection_uri_domain`,`app_id`,`tenant_id`,`third_party_id`), - FOREIGN KEY (`connection_uri_domain`, `app_id`, `tenant_id`) REFERENCES `tenant_configs` (`connection_uri_domain`, `app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE TABLE `tenant_thirdparty_provider_clients` ( - `connection_uri_domain` varchar(256) NOT NULL DEFAULT '', - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `third_party_id` varchar(28) NOT NULL, - `client_type` varchar(64) NOT NULL DEFAULT '', - `client_id` varchar(256) NOT NULL, - `client_secret` text, - `scope` text, - `force_pkce` tinyint(1) DEFAULT NULL, - `additional_config` text, - PRIMARY KEY (`connection_uri_domain`,`app_id`,`tenant_id`,`third_party_id`,`client_type`), - FOREIGN KEY (`connection_uri_domain`, `app_id`, `tenant_id`, `third_party_id`) REFERENCES `tenant_thirdparty_providers` (`connection_uri_domain`, `app_id`, `tenant_id`, `third_party_id`) ON DELETE CASCADE -); - -CREATE TABLE `tenant_first_factors` ( - connection_uri_domain VARCHAR(256) DEFAULT '', - app_id VARCHAR(64) DEFAULT 'public', - tenant_id VARCHAR(64) DEFAULT 'public', - factor_id VARCHAR(128), - PRIMARY KEY (`connection_uri_domain`, `app_id`, `tenant_id`, `factor_id`), - FOREIGN KEY (`connection_uri_domain`, `app_id`, `tenant_id`) REFERENCES `tenant_configs` (`connection_uri_domain`, `app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE TABLE `tenant_required_secondary_factors` ( - connection_uri_domain VARCHAR(256) DEFAULT '', - app_id VARCHAR(64) DEFAULT 'public', - tenant_id VARCHAR(64) DEFAULT 'public', - factor_id VARCHAR(128), - PRIMARY KEY (`connection_uri_domain`, `app_id`, `tenant_id`, `factor_id`), - FOREIGN KEY (`connection_uri_domain`, `app_id`, `tenant_id`) REFERENCES `tenant_configs` (`connection_uri_domain`, `app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE TABLE `key_value` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `name` varchar(128) NOT NULL, - `value` text, - `created_at_time` bigint unsigned DEFAULT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`name`), - FOREIGN KEY (`app_id`, `tenant_id`) REFERENCES `tenants` (`app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE TABLE `app_id_to_user_id` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `recipe_id` varchar(128) NOT NULL, - `primary_or_recipe_user_id` char(36) NOT NULL, - `is_linked_or_is_a_primary_user` BOOLEAN NOT NULL DEFAULT FALSE, - PRIMARY KEY (`app_id`,`user_id`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE, - FOREIGN KEY (`app_id`, `primary_or_recipe_user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE INDEX app_id_to_user_id_primary_user_id_index ON `app_id_to_user_id` (`primary_or_recipe_user_id`); - -CREATE INDEX app_id_to_user_id_user_id_index ON `app_id_to_user_id` (`user_id`); - -CREATE TABLE `all_auth_recipe_users` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `primary_or_recipe_user_id` char(36) NOT NULL, - `is_linked_or_is_a_primary_user` BOOLEAN NOT NULL DEFAULT FALSE, - `recipe_id` varchar(128) NOT NULL, - `time_joined` bigint unsigned NOT NULL, - `primary_or_recipe_user_time_joined` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`user_id`), - KEY `app_id` (`app_id`,`user_id`), - FOREIGN KEY (`app_id`, `tenant_id`) REFERENCES `tenants` (`app_id`, `tenant_id`) ON DELETE CASCADE, - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE, - FOREIGN KEY (`app_id`, `primary_or_recipe_user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE INDEX all_auth_recipe_users_pagination_index1 ON all_auth_recipe_users - (app_id, tenant_id, primary_or_recipe_user_time_joined DESC, primary_or_recipe_user_id DESC); - -CREATE INDEX all_auth_recipe_users_pagination_index3 ON all_auth_recipe_users - (recipe_id, app_id, tenant_id, primary_or_recipe_user_time_joined DESC, primary_or_recipe_user_id DESC); - -CREATE INDEX all_auth_recipe_users_primary_user_id_index ON all_auth_recipe_users - (primary_or_recipe_user_id, app_id); - -CREATE INDEX all_auth_recipe_users_recipe_id_index ON all_auth_recipe_users - (app_id, recipe_id, tenant_id); - -CREATE TABLE `userid_mapping` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `supertokens_user_id` char(36) NOT NULL, - `external_user_id` varchar(128) NOT NULL, - `external_user_id_info` text, - PRIMARY KEY (`app_id`,`supertokens_user_id`,`external_user_id`), - UNIQUE KEY `supertokens_user_id` (`app_id`,`supertokens_user_id`), - UNIQUE KEY `external_user_id` (`app_id`,`external_user_id`), - FOREIGN KEY (`app_id`, `supertokens_user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE TABLE `dashboard_users` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `email` varchar(256) NOT NULL, - `password_hash` varchar(256) NOT NULL, - `time_joined` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`user_id`), - UNIQUE KEY `email` (`app_id`,`email`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `dashboard_user_sessions` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `session_id` char(36) NOT NULL, - `user_id` char(36) NOT NULL, - `time_created` bigint unsigned NOT NULL, - `expiry` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`session_id`), - KEY `app_id` (`app_id`,`user_id`), - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `dashboard_users` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE INDEX `dashboard_user_sessions_expiry_index` ON `dashboard_user_sessions` (`expiry`); - -CREATE TABLE `session_access_token_signing_keys` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `created_at_time` bigint unsigned NOT NULL, - `value` text, - PRIMARY KEY (`app_id`,`created_at_time`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `session_info` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `session_handle` varchar(255) NOT NULL, - `user_id` varchar(128) NOT NULL, - `refresh_token_hash_2` varchar(128) NOT NULL, - `session_data` text, - `expires_at` bigint unsigned NOT NULL, - `created_at_time` bigint unsigned NOT NULL, - `jwt_user_payload` text, - `use_static_key` tinyint(1) NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`session_handle`), - FOREIGN KEY (`app_id`, `tenant_id`) REFERENCES `tenants` (`app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE INDEX `session_expiry_index` ON `session_info` (`expires_at`); - -CREATE TABLE `user_last_active` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - `last_active_time` bigint unsigned DEFAULT NULL, - PRIMARY KEY (`app_id`,`user_id`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `emailpassword_users` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `email` varchar(256) NOT NULL, - `password_hash` varchar(256) NOT NULL, - `time_joined` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`user_id`), - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE TABLE `emailpassword_user_to_tenant` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `email` varchar(256) NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`user_id`), - UNIQUE KEY `email` (`app_id`,`tenant_id`,`email`), - FOREIGN KEY (`app_id`, `tenant_id`, `user_id`) REFERENCES `all_auth_recipe_users` (`app_id`, `tenant_id`, `user_id`) ON DELETE CASCADE -); - -CREATE TABLE `emailpassword_pswd_reset_tokens` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `token` varchar(128) NOT NULL, - `token_expiry` bigint unsigned NOT NULL, - `email` varchar(256), - PRIMARY KEY (`app_id`,`user_id`,`token`), - UNIQUE KEY `token` (`token`), - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE ON UPDATE CASCADE -); - -CREATE INDEX `emailpassword_password_reset_token_expiry_index` ON `emailpassword_pswd_reset_tokens` (`token_expiry`); - -CREATE TABLE `emailverification_verified_emails` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - `email` varchar(256) NOT NULL, - PRIMARY KEY (`app_id`,`user_id`,`email`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `emailverification_tokens` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - `email` varchar(256) NOT NULL, - `token` varchar(128) NOT NULL, - `token_expiry` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`user_id`,`email`,`token`), - UNIQUE KEY `token` (`token`), - FOREIGN KEY (`app_id`, `tenant_id`) REFERENCES `tenants` (`app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE INDEX `emailverification_tokens_index` ON `emailverification_tokens` (`token_expiry`); - -CREATE TABLE `thirdparty_users` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `third_party_id` varchar(28) NOT NULL, - `third_party_user_id` varchar(256) NOT NULL, - `user_id` char(36) NOT NULL, - `email` varchar(256) NOT NULL, - `time_joined` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`user_id`), - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE INDEX `thirdparty_users_email_index` ON `thirdparty_users` (`app_id`,`email`); - -CREATE INDEX `thirdparty_users_thirdparty_user_id_index` ON `thirdparty_users` (`app_id`,`third_party_id`,`third_party_user_id`); - -CREATE TABLE `thirdparty_user_to_tenant` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `third_party_id` varchar(28) NOT NULL, - `third_party_user_id` varchar(256) NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`user_id`), - UNIQUE KEY `third_party_user_id` (`app_id`,`tenant_id`,`third_party_id`,`third_party_user_id`), - FOREIGN KEY (`app_id`, `tenant_id`, `user_id`) REFERENCES `all_auth_recipe_users` (`app_id`, `tenant_id`, `user_id`) ON DELETE CASCADE -); - -CREATE TABLE `passwordless_users` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `email` varchar(256) DEFAULT NULL, - `phone_number` varchar(256) DEFAULT NULL, - `time_joined` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`user_id`), - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE TABLE `passwordless_user_to_tenant` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `email` varchar(256) DEFAULT NULL, - `phone_number` varchar(256) DEFAULT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`user_id`), - UNIQUE KEY `email` (`app_id`,`tenant_id`,`email`), - UNIQUE KEY `phone_number` (`app_id`,`tenant_id`,`phone_number`), - FOREIGN KEY (`app_id`, `tenant_id`, `user_id`) REFERENCES `all_auth_recipe_users` (`app_id`, `tenant_id`, `user_id`) ON DELETE CASCADE -); - -CREATE TABLE `passwordless_devices` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `device_id_hash` char(44) NOT NULL, - `email` varchar(256) DEFAULT NULL, - `phone_number` varchar(256) DEFAULT NULL, - `link_code_salt` char(44) NOT NULL, - `failed_attempts` int unsigned NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`device_id_hash`), - FOREIGN KEY (`app_id`, `tenant_id`) REFERENCES `tenants` (`app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE INDEX `passwordless_devices_email_index` ON `passwordless_devices` (`app_id`,`tenant_id`,`email`); - -CREATE INDEX `passwordless_devices_phone_number_index` ON `passwordless_devices` (`app_id`,`tenant_id`,`phone_number`); - -CREATE TABLE `passwordless_codes` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `code_id` char(36) NOT NULL, - `device_id_hash` char(44) NOT NULL, - `link_code_hash` char(44) NOT NULL, - `created_at` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`code_id`), - UNIQUE KEY `link_code_hash` (`app_id`,`tenant_id`,`link_code_hash`), - KEY `app_id` (`app_id`,`tenant_id`,`device_id_hash`), - FOREIGN KEY (`app_id`, `tenant_id`, `device_id_hash`) REFERENCES `passwordless_devices` (`app_id`, `tenant_id`, `device_id_hash`) ON DELETE CASCADE ON UPDATE CASCADE -); - -CREATE INDEX `passwordless_codes_created_at_index` ON `passwordless_codes` (`app_id`,`tenant_id`,`created_at`); - -CREATE TABLE `jwt_signing_keys` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `key_id` varchar(255) NOT NULL, - `key_string` text NOT NULL, - `algorithm` varchar(10) NOT NULL, - `created_at` bigint unsigned DEFAULT NULL, - PRIMARY KEY (`app_id`,`key_id`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `user_metadata` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - `user_metadata` text NOT NULL, - PRIMARY KEY (`app_id`,`user_id`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `roles` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `role` varchar(255) NOT NULL, - PRIMARY KEY (`app_id`,`role`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `role_permissions` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `role` varchar(255) NOT NULL, - `permission` varchar(255) NOT NULL, - PRIMARY KEY (`app_id`,`role`,`permission`), - FOREIGN KEY (`app_id`, `role`) REFERENCES `roles` (`app_id`, `role`) ON DELETE CASCADE -); - -CREATE INDEX `role_permissions_permission_index` ON `role_permissions` (`app_id`,`permission`); - -CREATE TABLE `user_roles` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - `role` varchar(255) NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`user_id`,`role`), - FOREIGN KEY (`app_id`, `tenant_id`) REFERENCES `tenants` (`app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE INDEX `user_roles_role_index` ON `user_roles` (`app_id`,`tenant_id`,`role`); - -CREATE TABLE `totp_users` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - PRIMARY KEY (`app_id`,`user_id`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `totp_user_devices` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - `device_name` varchar(256) NOT NULL, - `secret_key` varchar(256) NOT NULL, - `period` int NOT NULL, - `skew` int NOT NULL, - `verified` tinyint(1) NOT NULL, - `created_at` BIGINT UNSIGNED, - PRIMARY KEY (`app_id`,`user_id`,`device_name`), - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `totp_users` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE TABLE `totp_used_codes` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - `code` varchar(8) NOT NULL, - `is_valid` tinyint(1) NOT NULL, - `expiry_time_ms` bigint unsigned NOT NULL, - `created_time_ms` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`user_id`,`created_time_ms`), - KEY `app_id` (`app_id`,`user_id`), - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `totp_users` (`app_id`, `user_id`) ON DELETE CASCADE, - FOREIGN KEY (`app_id`, `tenant_id`) REFERENCES `tenants` (`app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE INDEX `totp_used_codes_expiry_time_ms_index` ON `totp_used_codes` (`app_id`,`tenant_id`,`expiry_time_ms`); -``` - -:::tip -You also have the option to [rename these tables](./rename-database-tables). -::: - -## 4) Test the connection 🤞 -To test, start SuperTokens and run the following query in your database -```sql -SELECT * FROM key_value; -``` -If you see at least one row, it means that the connection has been successfully completed! 🥳🎉 - -:::tip blog -We also have [a blog post writeup](https://supertokens.com/blog/connect-supertokens-to-database) highlighting all the steps in more detail for different scenarios. -::: diff --git a/v2/thirdparty/custom-ui/init/database-setup/postgresql.mdx b/v2/thirdparty/custom-ui/init/database-setup/postgresql.mdx index abaea10fc..8375aa15f 100644 --- a/v2/thirdparty/custom-ui/init/database-setup/postgresql.mdx +++ b/v2/thirdparty/custom-ui/init/database-setup/postgresql.mdx @@ -4,601 +4,8 @@ title: If using PostgreSQL hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import SelfHostedTabs from "/src/components/tabs/SelfHostedTabs" -import TabItem from '@theme/TabItem'; -# PostgreSQL setup + -:::important -This is needed only if you are running the SuperTokens core yourself. - -The minimum required version is **PostgreSQL 9.6**. -::: - -:::tip blog -We also have [a blog post writeup](https://supertokens.com/blog/connect-supertokens-to-database) highlighting all the steps in more detail for different scenarios. -::: - -## 1) Create a database 🛠️ - -```sql -CREATE DATABASE supertokens; -``` -You can skip this step if you want SuperTokens to write to your own database. In this case, you will need to provide your database name as shown in the step below. - -## 2) Connect SuperTokens to your database 🔌 - - - - -:::caution -Host being `localhost` / `127.0.0.1` will not work in a docker image. Instead, please provide the database's local / public hostname or IP address. - -You also need to make the database listen on all the IP's of the local machine. -This can be done by editing the `postgresql.conf` config file and setting the value of `listen_addresses` to `0.0.0.0`. -::: - -```bash - -docker run \ - -p 3567:3567 \ - // highlight-next-line - -e POSTGRESQL_CONNECTION_URI="postgresql://username:pass@host/dbName" \ - -d registry.supertokens.io/supertokens/supertokens-postgresql - -# OR - -docker run \ - -p 3567:3567 \ - // highlight-start - -e POSTGRESQL_USER="username" \ - -e POSTGRESQL_PASSWORD="password" \ - -e POSTGRESQL_HOST="host" \ - -e POSTGRESQL_PORT="5432" \ - -e POSTGRESQL_DATABASE_NAME="supertokens" \ - // highlight-end - -d registry.supertokens.io/supertokens/supertokens-postgresql -``` - -:::tip -You can also provide the table schema by providing the `POSTGRESQL_TABLE_SCHEMA` option. -::: - - - - - -```yaml -# You need to add the following to the config.yaml file. -# The file path can be found by running the "supertokens --help" command - -postgresql_connection_uri: "postgresql://username:pass@host/dbName" - -# OR - -postgresql_user: "username" - -postgresql_password: "password" - -postgresql_host: "host" - -postgresql_port: "5432" - -postgresql_database_name: "supertokens" -``` - -- You can also provide the table schema by providing the `postgresql_table_schema` option. - - - - - -## 3) Create tables 👩‍💻👨‍💻 - -:::note -This happens automatically, unless you provide a PostgreSQL user that doesn't have table creation permission. -::: - -```sql -CREATE TABLE apps ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - created_at_time BIGINT, - CONSTRAINT apps_pkey PRIMARY KEY (app_id) -); - -CREATE TABLE tenants ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - created_at_time BIGINT, - CONSTRAINT tenants_pkey PRIMARY KEY (app_id, tenant_id), - CONSTRAINT tenants_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX tenants_app_id_index ON tenants (app_id); - -CREATE TABLE tenant_configs ( - connection_uri_domain VARCHAR(256) DEFAULT '' NOT NULL, - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - core_config TEXT, - email_password_enabled BOOLEAN, - passwordless_enabled BOOLEAN, - third_party_enabled BOOLEAN, - is_first_factors_null BOOLEAN, - CONSTRAINT tenant_configs_pkey PRIMARY KEY (connection_uri_domain, app_id, tenant_id) -); - -CREATE TABLE tenant_thirdparty_providers ( - connection_uri_domain VARCHAR(256) DEFAULT '' NOT NULL, - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - third_party_id VARCHAR(28) NOT NULL, - name VARCHAR(64), - authorization_endpoint TEXT, - authorization_endpoint_query_params TEXT, - token_endpoint TEXT, - token_endpoint_body_params TEXT, - user_info_endpoint TEXT, - user_info_endpoint_query_params TEXT, - user_info_endpoint_headers TEXT, - jwks_uri TEXT, - oidc_discovery_endpoint TEXT, - require_email BOOLEAN, - user_info_map_from_id_token_payload_user_id VARCHAR(64), - user_info_map_from_id_token_payload_email VARCHAR(64), - user_info_map_from_id_token_payload_email_verified VARCHAR(64), - user_info_map_from_user_info_endpoint_user_id VARCHAR(64), - user_info_map_from_user_info_endpoint_email VARCHAR(64), - user_info_map_from_user_info_endpoint_email_verified VARCHAR(64), - CONSTRAINT tenant_thirdparty_providers_pkey PRIMARY KEY (connection_uri_domain, app_id, tenant_id, third_party_id), - CONSTRAINT tenant_thirdparty_providers_tenant_id_fkey FOREIGN KEY (connection_uri_domain, app_id, tenant_id) REFERENCES public.tenant_configs(connection_uri_domain, app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX tenant_thirdparty_providers_tenant_id_index ON tenant_thirdparty_providers (connection_uri_domain, app_id, tenant_id); - -CREATE TABLE tenant_thirdparty_provider_clients ( - connection_uri_domain VARCHAR(256) DEFAULT '' NOT NULL, - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - third_party_id VARCHAR(28) NOT NULL, - client_type VARCHAR(64) DEFAULT '' NOT NULL, - client_id VARCHAR(256) NOT NULL, - client_secret TEXT, - scope VARCHAR(128)[], - force_pkce BOOLEAN, - additional_config TEXT, - CONSTRAINT tenant_thirdparty_provider_clients_pkey PRIMARY KEY (connection_uri_domain, app_id, tenant_id, third_party_id, client_type), - CONSTRAINT tenant_thirdparty_provider_clients_third_party_id_fkey FOREIGN KEY (connection_uri_domain, app_id, tenant_id, third_party_id) REFERENCES public.tenant_thirdparty_providers(connection_uri_domain, app_id, tenant_id, third_party_id) ON DELETE CASCADE -); - -CREATE INDEX tenant_thirdparty_provider_clients_third_party_id_index ON tenant_thirdparty_provider_clients (connection_uri_domain, app_id, tenant_id, third_party_id); - -CREATE TABLE tenant_first_factors ( - connection_uri_domain VARCHAR(256) DEFAULT '' NOT NULL, - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - factor_id VARCHAR(128), - CONSTRAINT tenant_first_factors_pkey PRIMARY KEY (connection_uri_domain, app_id, tenant_id, factor_id), - CONSTRAINT tenant_first_factors_tenant_id_fkey FOREIGN KEY (connection_uri_domain, app_id, tenant_id) REFERENCES public.tenant_configs(connection_uri_domain, app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX IF NOT EXISTS tenant_first_factors_tenant_id_index ON tenant_first_factors (connection_uri_domain, app_id, tenant_id); - -CREATE TABLE tenant_required_secondary_factors ( - connection_uri_domain VARCHAR(256) DEFAULT '' NOT NULL, - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - factor_id VARCHAR(128), - CONSTRAINT tenant_required_secondary_factors_pkey PRIMARY KEY (connection_uri_domain, app_id, tenant_id, factor_id), - CONSTRAINT tenant_required_secondary_factors_tenant_id_fkey FOREIGN KEY (connection_uri_domain, app_id, tenant_id) REFERENCES public.tenant_configs(connection_uri_domain, app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX IF NOT EXISTS tenant_default_required_factor_ids_tenant_id_index ON tenant_required_secondary_factors (connection_uri_domain, app_id, tenant_id); - -CREATE TABLE key_value ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - name VARCHAR(128) NOT NULL, - value TEXT, - created_at_time BIGINT, - CONSTRAINT key_value_pkey PRIMARY KEY (app_id, tenant_id, name), - CONSTRAINT key_value_tenant_id_fkey FOREIGN KEY (app_id, tenant_id) REFERENCES public.tenants(app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX key_value_tenant_id_index ON key_value (app_id, tenant_id); - -CREATE TABLE app_id_to_user_id ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - recipe_id VARCHAR(128) NOT NULL, - primary_or_recipe_user_id CHAR(36) NOT NULL, - is_linked_or_is_a_primary_user BOOLEAN NOT NULL DEFAULT FALSE, - CONSTRAINT app_id_to_user_id_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT app_id_to_user_id_primary_or_recipe_user_id_fkey FOREIGN KEY(app_id, primary_or_recipe_user_id) REFERENCES app_id_to_user_id (app_id, user_id) ON DELETE CASCADE, - CONSTRAINT app_id_to_user_id_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX app_id_to_user_id_app_id_index ON app_id_to_user_id (app_id); - -CREATE INDEX app_id_to_user_id_primary_user_id_index ON app_id_to_user_id (primary_or_recipe_user_id, app_id); - -CREATE TABLE all_auth_recipe_users ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - primary_or_recipe_user_id CHAR(36) NOT NULL, - is_linked_or_is_a_primary_user BOOLEAN NOT NULL DEFAULT FALSE, - recipe_id VARCHAR(128) NOT NULL, - time_joined BIGINT NOT NULL, - primary_or_recipe_user_time_joined BIGINT NOT NULL, - CONSTRAINT all_auth_recipe_users_pkey PRIMARY KEY (app_id, tenant_id, user_id), - CONSTRAINT all_auth_recipe_users_tenant_id_fkey FOREIGN KEY (app_id, tenant_id) REFERENCES public.tenants(app_id, tenant_id) ON DELETE CASCADE, - CONSTRAINT all_auth_recipe_users_primary_or_recipe_user_id_fkey FOREIGN KEY(app_id, primary_or_recipe_user_id) REFERENCES public.app_id_to_user_id (app_id, user_id) ON DELETE CASCADE, - CONSTRAINT all_auth_recipe_users_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.app_id_to_user_id(app_id, user_id) ON DELETE CASCADE -); - -CREATE INDEX all_auth_recipe_users_pagination_index1 ON all_auth_recipe_users - (app_id, tenant_id, primary_or_recipe_user_time_joined DESC, primary_or_recipe_user_id DESC); - -CREATE INDEX all_auth_recipe_users_pagination_index2 ON all_auth_recipe_users - (app_id, tenant_id, primary_or_recipe_user_time_joined ASC, primary_or_recipe_user_id DESC); - -CREATE INDEX all_auth_recipe_users_pagination_index3 ON all_auth_recipe_users - (recipe_id, app_id, tenant_id, primary_or_recipe_user_time_joined DESC, primary_or_recipe_user_id DESC); - - -CREATE INDEX all_auth_recipe_users_pagination_index4 ON all_auth_recipe_users - (recipe_id, app_id, tenant_id, primary_or_recipe_user_time_joined ASC, primary_or_recipe_user_id DESC); - - -CREATE INDEX all_auth_recipe_users_primary_user_id_index ON all_auth_recipe_users - (primary_or_recipe_user_id, app_id); - -CREATE INDEX all_auth_recipe_users_recipe_id_index ON all_auth_recipe_users - (app_id, recipe_id, tenant_id); - -CREATE INDEX all_auth_recipe_user_id_index ON all_auth_recipe_users (app_id, user_id); - -CREATE INDEX all_auth_recipe_tenant_id_index ON all_auth_recipe_users (app_id, tenant_id); - -CREATE TABLE userid_mapping ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - supertokens_user_id character(36) NOT NULL, - external_user_id VARCHAR(128) NOT NULL, - external_user_id_info TEXT, - CONSTRAINT userid_mapping_external_user_id_key UNIQUE (app_id, external_user_id), - CONSTRAINT userid_mapping_pkey PRIMARY KEY (app_id, supertokens_user_id, external_user_id), - CONSTRAINT userid_mapping_supertokens_user_id_key UNIQUE (app_id, supertokens_user_id), - CONSTRAINT userid_mapping_supertokens_user_id_fkey FOREIGN KEY (app_id, supertokens_user_id) REFERENCES public.app_id_to_user_id(app_id, user_id) ON DELETE CASCADE -); - -CREATE INDEX userid_mapping_supertokens_user_id_index ON userid_mapping (app_id, supertokens_user_id); - -CREATE TABLE dashboard_users ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - email VARCHAR(256) NOT NULL, - password_hash VARCHAR(256) NOT NULL, - time_joined BIGINT NOT NULL, - CONSTRAINT dashboard_users_email_key UNIQUE (app_id, email), - CONSTRAINT dashboard_users_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT dashboard_users_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX dashboard_users_app_id_index ON dashboard_users (app_id); - -CREATE TABLE dashboard_user_sessions ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - session_id character(36) NOT NULL, - user_id character(36) NOT NULL, - time_created BIGINT NOT NULL, - expiry BIGINT NOT NULL, - CONSTRAINT dashboard_user_sessions_pkey PRIMARY KEY (app_id, session_id), - CONSTRAINT dashboard_user_sessions_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.dashboard_users(app_id, user_id) ON UPDATE CASCADE ON DELETE CASCADE -); - -CREATE INDEX dashboard_user_sessions_expiry_index ON dashboard_user_sessions (expiry); - -CREATE INDEX dashboard_user_sessions_user_id_index ON dashboard_user_sessions (app_id, user_id); - -CREATE TABLE session_access_token_signing_keys ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - created_at_time BIGINT NOT NULL, - value TEXT, - CONSTRAINT session_access_token_signing_keys_pkey PRIMARY KEY (app_id, created_at_time), - CONSTRAINT session_access_token_signing_keys_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX access_token_signing_keys_app_id_index ON session_access_token_signing_keys (app_id); - -CREATE TABLE session_info ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - session_handle VARCHAR(255) NOT NULL, - user_id VARCHAR(128) NOT NULL, - refresh_token_hash_2 VARCHAR(128) NOT NULL, - session_data TEXT, - expires_at BIGINT NOT NULL, - created_at_time BIGINT NOT NULL, - jwt_user_payload TEXT, - use_static_key BOOLEAN NOT NULL, - CONSTRAINT session_info_pkey PRIMARY KEY (app_id, tenant_id, session_handle), - CONSTRAINT session_info_tenant_id_fkey FOREIGN KEY (app_id, tenant_id) REFERENCES public.tenants(app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX session_expiry_index ON session_info (expires_at); - -CREATE INDEX session_info_tenant_id_index ON session_info (app_id, tenant_id); - -CREATE TABLE user_last_active ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - last_active_time BIGINT, - CONSTRAINT user_last_active_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT user_last_active_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX user_last_active_app_id_index ON user_last_active (app_id); - -CREATE TABLE emailpassword_users ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - email VARCHAR(256) NOT NULL, - password_hash VARCHAR(256) NOT NULL, - time_joined BIGINT NOT NULL, - CONSTRAINT emailpassword_users_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT emailpassword_users_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.app_id_to_user_id(app_id, user_id) ON DELETE CASCADE -); - -CREATE TABLE emailpassword_user_to_tenant ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - email VARCHAR(256) NOT NULL, - CONSTRAINT emailpassword_user_to_tenant_email_key UNIQUE (app_id, tenant_id, email), - CONSTRAINT emailpassword_user_to_tenant_pkey PRIMARY KEY (app_id, tenant_id, user_id), - CONSTRAINT emailpassword_user_to_tenant_user_id_fkey FOREIGN KEY (app_id, tenant_id, user_id) REFERENCES public.all_auth_recipe_users(app_id, tenant_id, user_id) ON DELETE CASCADE -); - -CREATE TABLE emailpassword_pswd_reset_tokens ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - token VARCHAR(128) NOT NULL, - token_expiry BIGINT NOT NULL, - email VARCHAR(256), - CONSTRAINT emailpassword_pswd_reset_tokens_pkey PRIMARY KEY (app_id, user_id, token), - CONSTRAINT emailpassword_pswd_reset_tokens_token_key UNIQUE (token), - CONSTRAINT emailpassword_pswd_reset_tokens_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.app_id_to_user_id(app_id, user_id) ON UPDATE CASCADE ON DELETE CASCADE -); - -CREATE INDEX emailpassword_password_reset_token_expiry_index ON emailpassword_pswd_reset_tokens (token_expiry); - -CREATE INDEX emailpassword_pswd_reset_tokens_user_id_index ON emailpassword_pswd_reset_tokens (app_id, user_id); - -CREATE TABLE emailverification_verified_emails ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - email VARCHAR(256) NOT NULL, - CONSTRAINT emailverification_verified_emails_pkey PRIMARY KEY (app_id, user_id, email), - CONSTRAINT emailverification_verified_emails_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX emailverification_verified_emails_app_id_index ON emailverification_verified_emails (app_id); - -CREATE TABLE emailverification_tokens ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - email VARCHAR(256) NOT NULL, - token VARCHAR(128) NOT NULL, - token_expiry BIGINT NOT NULL, - CONSTRAINT emailverification_tokens_pkey PRIMARY KEY (app_id, tenant_id, user_id, email, token), - CONSTRAINT emailverification_tokens_token_key UNIQUE (token), - CONSTRAINT emailverification_tokens_tenant_id_fkey FOREIGN KEY (app_id, tenant_id) REFERENCES public.tenants(app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX emailverification_tokens_index ON emailverification_tokens (token_expiry); - -CREATE INDEX emailverification_tokens_tenant_id_index ON emailverification_tokens (app_id, tenant_id); - -CREATE TABLE thirdparty_users ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - third_party_id VARCHAR(28) NOT NULL, - third_party_user_id VARCHAR(256) NOT NULL, - user_id character(36) NOT NULL, - email VARCHAR(256) NOT NULL, - time_joined BIGINT NOT NULL, - CONSTRAINT thirdparty_users_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT thirdparty_users_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.app_id_to_user_id(app_id, user_id) ON DELETE CASCADE -); - -CREATE INDEX thirdparty_users_email_index ON thirdparty_users (app_id, email); - -CREATE INDEX thirdparty_users_thirdparty_user_id_index ON thirdparty_users (app_id, third_party_id, third_party_user_id); - -CREATE TABLE thirdparty_user_to_tenant ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - third_party_id VARCHAR(28) NOT NULL, - third_party_user_id VARCHAR(256) NOT NULL, - CONSTRAINT thirdparty_user_to_tenant_pkey PRIMARY KEY (app_id, tenant_id, user_id), - CONSTRAINT thirdparty_user_to_tenant_third_party_user_id_key UNIQUE (app_id, tenant_id, third_party_id, third_party_user_id), - CONSTRAINT thirdparty_user_to_tenant_user_id_fkey FOREIGN KEY (app_id, tenant_id, user_id) REFERENCES public.all_auth_recipe_users(app_id, tenant_id, user_id) ON DELETE CASCADE -); - -CREATE TABLE passwordless_users ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - email VARCHAR(256), - phone_number VARCHAR(256), - time_joined BIGINT NOT NULL, - CONSTRAINT passwordless_users_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT passwordless_users_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.app_id_to_user_id(app_id, user_id) ON DELETE CASCADE -); - -CREATE TABLE passwordless_user_to_tenant ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - email VARCHAR(256), - phone_number VARCHAR(256), - CONSTRAINT passwordless_user_to_tenant_email_key UNIQUE (app_id, tenant_id, email), - CONSTRAINT passwordless_user_to_tenant_phone_number_key UNIQUE (app_id, tenant_id, phone_number), - CONSTRAINT passwordless_user_to_tenant_pkey PRIMARY KEY (app_id, tenant_id, user_id), - CONSTRAINT passwordless_user_to_tenant_user_id_fkey FOREIGN KEY (app_id, tenant_id, user_id) REFERENCES public.all_auth_recipe_users(app_id, tenant_id, user_id) ON DELETE CASCADE -); - -CREATE TABLE passwordless_devices ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - device_id_hash character(44) NOT NULL, - email VARCHAR(256), - phone_number VARCHAR(256), - link_code_salt character(44) NOT NULL, - failed_attempts integer NOT NULL, - CONSTRAINT passwordless_devices_pkey PRIMARY KEY (app_id, tenant_id, device_id_hash), - CONSTRAINT passwordless_devices_tenant_id_fkey FOREIGN KEY (app_id, tenant_id) REFERENCES public.tenants(app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX passwordless_devices_email_index ON passwordless_devices (app_id, tenant_id, email); - -CREATE INDEX passwordless_devices_phone_number_index ON passwordless_devices (app_id, tenant_id, phone_number); - -CREATE INDEX passwordless_devices_tenant_id_index ON passwordless_devices (app_id, tenant_id); - -CREATE TABLE passwordless_codes ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - code_id character(36) NOT NULL, - device_id_hash character(44) NOT NULL, - link_code_hash character(44) NOT NULL, - created_at BIGINT NOT NULL, - CONSTRAINT passwordless_codes_link_code_hash_key UNIQUE (app_id, tenant_id, link_code_hash), - CONSTRAINT passwordless_codes_pkey PRIMARY KEY (app_id, tenant_id, code_id), - CONSTRAINT passwordless_codes_device_id_hash_fkey FOREIGN KEY (app_id, tenant_id, device_id_hash) REFERENCES public.passwordless_devices(app_id, tenant_id, device_id_hash) ON UPDATE CASCADE ON DELETE CASCADE -); - -CREATE INDEX passwordless_codes_created_at_index ON passwordless_codes (app_id, tenant_id, created_at); - -CREATE INDEX passwordless_codes_device_id_hash_index ON passwordless_codes (app_id, tenant_id, device_id_hash); - -CREATE TABLE jwt_signing_keys ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - key_id VARCHAR(255) NOT NULL, - key_string TEXT NOT NULL, - algorithm VARCHAR(10) NOT NULL, - created_at BIGINT, - CONSTRAINT jwt_signing_keys_pkey PRIMARY KEY (app_id, key_id), - CONSTRAINT jwt_signing_keys_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX jwt_signing_keys_app_id_index ON jwt_signing_keys (app_id); - -CREATE TABLE user_metadata ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - user_metadata TEXT NOT NULL, - CONSTRAINT user_metadata_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT user_metadata_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX user_metadata_app_id_index ON user_metadata (app_id); - -CREATE TABLE roles ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - role VARCHAR(255) NOT NULL, - CONSTRAINT roles_pkey PRIMARY KEY (app_id, role), - CONSTRAINT roles_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX roles_app_id_index ON roles (app_id); - -CREATE TABLE role_permissions ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - role VARCHAR(255) NOT NULL, - permission VARCHAR(255) NOT NULL, - CONSTRAINT role_permissions_pkey PRIMARY KEY (app_id, role, permission), - CONSTRAINT role_permissions_role_fkey FOREIGN KEY (app_id, role) REFERENCES public.roles(app_id, role) ON DELETE CASCADE -); - -CREATE INDEX role_permissions_permission_index ON role_permissions (app_id, permission); - -CREATE INDEX role_permissions_role_index ON role_permissions (app_id, role); - -CREATE TABLE user_roles ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - role VARCHAR(255) NOT NULL, - CONSTRAINT user_roles_pkey PRIMARY KEY (app_id, tenant_id, user_id, role), - CONSTRAINT user_roles_tenant_id_fkey FOREIGN KEY (app_id, tenant_id) REFERENCES public.tenants(app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX user_roles_role_index ON user_roles (app_id, tenant_id, role); - -CREATE INDEX user_roles_tenant_id_index ON user_roles (app_id, tenant_id); - -CREATE INDEX user_roles_app_id_role_index ON user_roles (app_id, role); - -CREATE TABLE totp_users ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - CONSTRAINT totp_users_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT totp_users_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX totp_users_app_id_index ON totp_users (app_id); - -CREATE TABLE totp_user_devices ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - device_name VARCHAR(256) NOT NULL, - secret_key VARCHAR(256) NOT NULL, - period integer NOT NULL, - skew integer NOT NULL, - verified BOOLEAN NOT NULL, - created_at BIGINT, - CONSTRAINT totp_user_devices_pkey PRIMARY KEY (app_id, user_id, device_name), - CONSTRAINT totp_user_devices_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.totp_users(app_id, user_id) ON DELETE CASCADE -); - -CREATE INDEX totp_user_devices_user_id_index ON totp_user_devices (app_id, user_id); - -CREATE TABLE totp_used_codes ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - code VARCHAR(8) NOT NULL, - is_valid BOOLEAN NOT NULL, - expiry_time_ms BIGINT NOT NULL, - created_time_ms BIGINT NOT NULL, - CONSTRAINT totp_used_codes_pkey PRIMARY KEY (app_id, tenant_id, user_id, created_time_ms), - CONSTRAINT totp_used_codes_tenant_id_fkey FOREIGN KEY (app_id, tenant_id) REFERENCES public.tenants(app_id, tenant_id) ON DELETE CASCADE, - CONSTRAINT totp_used_codes_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.totp_users(app_id, user_id) ON DELETE CASCADE -); - -CREATE INDEX totp_used_codes_expiry_time_ms_index ON totp_used_codes (app_id, tenant_id, expiry_time_ms); - -CREATE INDEX totp_used_codes_tenant_id_index ON totp_used_codes (app_id, tenant_id); - -CREATE INDEX totp_used_codes_user_id_index ON totp_used_codes (app_id, user_id); -``` - -:::tip -You also have the option to [rename these tables](./rename-database-tables). -::: - -## 4) Test the connection 🤞 -To test, start SuperTokens and run the following query in your database -```sql -SELECT * FROM key_value; -``` -If you see at least one row, it means that the connection has been successfully completed! 🥳🎉 - -:::tip blog -We also have [a blog post writeup](https://supertokens.com/blog/connect-supertokens-to-database) highlighting all the steps in more detail for different scenarios. -::: diff --git a/v2/thirdparty/custom-ui/init/database-setup/rename-database-tables.mdx b/v2/thirdparty/custom-ui/init/database-setup/rename-database-tables.mdx index 93f84ccc6..e02f73ba3 100644 --- a/v2/thirdparty/custom-ui/init/database-setup/rename-database-tables.mdx +++ b/v2/thirdparty/custom-ui/init/database-setup/rename-database-tables.mdx @@ -4,95 +4,8 @@ title: Rename database tables hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import SelfHostedTabs from "/src/components/tabs/SelfHostedTabs" -import TabItem from '@theme/TabItem'; -# Rename database tables + -:::caution -If you already have tables created by SuperTokens, and then you rename them, SuperTokens will create new tables. So please be sure to migrate the data from the existing one to the new one. -::: - -You can add a prefix to all table names that are managed by SuperTokens. This way, all of them will be renamed in a way that have no clashes with your tables. - -For example, two tables created by SuperTokens are called `emailpassword_users` and `thirdparty_users`. If you add a prefix to them (something like `"my_prefix"`), then the tables will be renamed to `my_prefix_emailpassword_users` and `my_prefix_thirdparty_users`. - -## For MySQL - - - - -```bash -docker run \ - -p 3567:3567 \ - // highlight-next-line - -e MYSQL_TABLE_NAMES_PREFIX="my_prefix" \ - -d registry.supertokens.io/supertokens/supertokens-mysql -``` - - - - -```yaml -# You need to add the following to the config.yaml file. -# The file path can be found by running the "supertokens --help" command - -mysql_table_names_prefix: "my_prefix" -``` - - - -## For PostgreSQL - - - - -```bash -docker run \ - -p 3567:3567 \ - // highlight-next-line - -e POSTGRESQL_TABLE_NAMES_PREFIX="my_prefix" \ - -d registry.supertokens.io/supertokens/supertokens-postgresql -``` - - - - -```yaml -# You need to add the following to the config.yaml file. -# The file path can be found by running the "supertokens --help" command - -postgresql_table_names_prefix: "my_prefix" -``` - - - -## For MongoDB - - - - -```bash -docker run \ - -p 3567:3567 \ - // highlight-next-line - -e MONGODB_COLLECTION_NAMES_PREFIX="my_prefix" \ - -d registry.supertokens.io/supertokens/supertokens-mongodb -``` - - - - - -```yaml -# You need to add the following to the config.yaml file. -# The file path can be found by running the "supertokens --help" command - -mongodb_collection_names_prefix: "my_prefix" -``` - - - diff --git a/v2/thirdparty/custom-ui/init/frontend.mdx b/v2/thirdparty/custom-ui/init/frontend.mdx index 0632392b6..bac6ce88b 100644 --- a/v2/thirdparty/custom-ui/init/frontend.mdx +++ b/v2/thirdparty/custom-ui/init/frontend.mdx @@ -4,315 +4,8 @@ title: "Step 1: Frontend" hide_title: true --- +import Redirector from '/src/components/Redirector'; -import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" -import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" -import TabItem from '@theme/TabItem'; -import {Question, Answer}from "/src/components/question" -import AppInfoForm from "/src/components/appInfoForm" -import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" -import WebJsInjector from "/src/components/webJsInjector" -# Frontend Integration + -## Supported frameworks - -import TechStackSupport from "../../../community/reusableMD/supported-tech-stacks-frontend-custom-ui.mdx" - - - -## 1) Install - - - - - - - -```bash -npm i -s supertokens-web-js -``` - - - - -You need to add all of the following scripts to your app - - - -```bash - - - - -``` - - - - - - - - - - -:::info - -If you want to implement a common authencation experience for both web and mobile, please look at our [**Unified Login guide**](/docs/unified-login/introduction). - -::: - - - - - -```bash -npm i -s supertokens-react-native -# IMPORTANT: If you already have @react-native-async-storage/async-storage as a dependency, make sure the version is 1.12.1 or higher -npm i -s @react-native-async-storage/async-storage -``` - - - - - -Add to your `settings.gradle`: -```bash -dependencyResolutionManagement { - ... - repositories { - ... - maven { url 'https://jitpack.io' } - } -} -``` - -Add the following to you app level's `build.gradle`: -```bash -implementation 'com.github.supertokens:supertokens-android:X.Y.Z' -``` - -You can find the latest version of the SDK [here](https://github.com/supertokens/supertokens-android/releases) (ignore the `v` prefix in the releases). - - - - - -#### Using Cocoapods - -Add the Cocoapod dependency to your Podfile - -```bash -pod 'SuperTokensIOS' -``` - -#### Using Swift Package Manager - -Follow the [official documentation](https://developer.apple.com/documentation/xcode/adding-package-dependencies-to-your-app) to learn how to use Swift Package Manager to add dependencies to your project. - -When adding the dependency use the `master` branch after you enter the supertokens-ios repository URL: - -```bash -https://github.com/supertokens/supertokens-ios -``` - - - - - -Add the dependency to your pubspec.yaml - -```bash -supertokens_flutter: ^X.Y.Z -``` - -You can find the latest version of the SDK [here](https://github.com/supertokens/supertokens-flutter/releases) (ignore the `v` prefix in the releases). - - - - - - - - - -## 2) Call the `init` function - - - - - - - - - -Call the following `init` function at the start of your app (ideally on the global scope). - -```tsx -import SuperTokens from 'supertokens-web-js'; -import Session from 'supertokens-web-js/recipe/session'; -import ^{recipeNameCapitalLetters} from 'supertokens-web-js/recipe/^{codeImportRecipeName}' - -SuperTokens.init({ - appInfo: { - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}", - appName: "...", - }, - recipeList: [ - Session.init(), - ^{recipeNameCapitalLetters}.init(), - ], -}); -``` - - - - - - - - -Call the following `init` function at the start of your app (ideally on the global scope). - -```tsx -import supertokens from "supertokens-web-js-script"; -import supertokensSession from "supertokens-web-js-script/recipe/session"; -import supertokens^{recipeNameCapitalLetters} from 'supertokens-web-js-script/recipe/^{codeImportRecipeName}' -supertokens.init({ - appInfo: { - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}", - appName: "...", - }, - recipeList: [ - supertokensSession.init(), - supertokens^{recipeNameCapitalLetters}.init(), - ], -}); -``` - - - - - - - - - - - - - - - - -Call the following `init` function at the start of your app (ideally on the global scope). - -```tsx -import SuperTokens from 'supertokens-react-native'; - -SuperTokens.init({ - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}", -}); -``` - - - - - - - - - -Add the `SuperTokens.init` function call at the start of your application. - -```kotlin -import android.app.Application -import com.supertokens.session.SuperTokens - -class MainApplication: Application() { - override fun onCreate() { - super.onCreate() - - SuperTokens.Builder(this, "^{form_apiDomain}") - .apiBasePath("^{form_apiBasePath}") - .build() - } -} -``` - - - - - - - - - -Add the `SuperTokens.initialize` function call at the start of your application. - -```swift -import UIKit -import SuperTokensIOS - -fileprivate class ApplicationDelegate: UIResponder, UIApplicationDelegate { - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - do { - try SuperTokens.initialize( - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}" - ) - } catch SuperTokensError.initError(let message) { - // TODO: Handle initialization error - } catch { - // Some other error - } - - return true - } - -} -``` - - - - - - - - - -Add the `SuperTokens.init` function call at the start of your application. - -```dart -import 'package:supertokens_flutter/supertokens.dart'; - -void main() { - SuperTokens.init( - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}", - ); -} -``` - - - - - - - - - - - -## What to do next? -The above code snippet sets up session management network interceptors on the frontend. Our frontend SDK will now be able to automatically save and add session tokens to each request to your API layer and also do auto session refreshing. - -The next steps are to: -- Step 2: setup the backend SDK in your API layer -- Step 3: Setup the SuperTokens core (sign up for managed service, or self host it) -- Step 4: Enable the user management dashboard -- Use the frontend SDK's helper functions to build your own UI - follow along the docs in the "Using your own UI" section and you will find docs for this after "Step 4". diff --git a/v2/thirdparty/custom-ui/init/user-management-dashboard/managing-user-roles-and-permissions.mdx b/v2/thirdparty/custom-ui/init/user-management-dashboard/managing-user-roles-and-permissions.mdx index ec916c7a2..ae431bb4c 100644 --- a/v2/thirdparty/custom-ui/init/user-management-dashboard/managing-user-roles-and-permissions.mdx +++ b/v2/thirdparty/custom-ui/init/user-management-dashboard/managing-user-roles-and-permissions.mdx @@ -4,157 +4,7 @@ title: "Managing user roles and permissions" hide_title: true --- +import Redirector from '/src/components/Redirector'; - - - + -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import TabItem from '@theme/TabItem'; - -# Managing user roles and permissions - -You can manage [user roles and permissions](/docs/userroles/introduction) of your app from the user management dashboard. - -## Initialisation - -To begin configuring user roles and permissions on the user management dashboard, start by initializing the "UserRoles" recipe in the `recipeList` on the backend. - - - - -```tsx -import SuperTokens from "supertokens-node"; -import Dashboard from "supertokens-node/recipe/dashboard"; -import UserRoles from "supertokens-node/recipe/userroles" - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // TODO: Initialise other recipes if needed. - Dashboard.init(), - // highlight-start - UserRoles.init() - // highlight-end - ], -}); -``` -:::important Note - -Please note that the capability to manage roles and permissions from the user management dashboard is available only from Node SDK version `v16.6.0` onwards. - -::: - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/dashboard" - "github.com/supertokens/supertokens-golang/recipe/userroles" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - supertokens.Init(supertokens.TypeInput{ - RecipeList: []supertokens.Recipe{ - // TODO: Initialise other recipes if needed - dashboard.Init(nil), - // highlight-start - userroles.Init(nil), - // highlight-end - }, - }); -} -``` - - - - - -```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import dashboard, userroles - -init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore - recipe_list=[ - # TODO: Initialise other recipes if needed. - dashboard.init(), - # highlight-start - userroles.init() - # highlight-end - ] -) -``` - - - - - - - -## Managing roles and permissions - -When you first use the `UserRoles` recipe, the list of roles will be empty. To create roles, simply click on the "Add Role" button. - -No roles created - -This action will open a modal, enabling you to create a role along with its associated permissions. Permissions are essentially a list of strings assigned to a specific role. - -Create role - -After creating a role, the UI should display a list of all roles in your app. - -Roles list - -You can now preview the role you created by clicking on the role row. The modal provides options to edit or delete the role. - -Preview role - -## Manging roles and users - -To assign a specific role to a user, start by finding the user in the dashboard. Upon clicking the user, navigate to the user details page where you'll find a section for user roles. - -If the selected user is associated with multiple tenants, you can choose a 'tenantId' from the dropdown menu to specify the tenant for which you'd like to assign roles. - -Select tenant - -Click the edit button to start assigning roles. Then, select the "Assign Role" button, and a modal will appear with a list of available roles for assignment to this user. - -Assign role - -To remove a role assigned to a user, simply click on the "X" icon next to that specific role. - -View assigned role - - - - -:::important - -Our Python SDK doesn't currently support roles management via the user management UI. Instead, you can use the functions exposed by the SDK to manage roles and permissions. Check out the guide [here](/docs/userroles/introduction). - -::: - - - - - -:::important - -Our Golang SDK doesn't currently support roles management via the user management UI. Instead, you can use the functions exposed by the SDK to manage roles and permissions. Check out the guide [here](/docs/userroles/introduction). - -::: - - - - - - - diff --git a/v2/thirdparty/custom-ui/init/user-management-dashboard/setup.mdx b/v2/thirdparty/custom-ui/init/user-management-dashboard/setup.mdx index 0c108af83..ce23dfa60 100644 --- a/v2/thirdparty/custom-ui/init/user-management-dashboard/setup.mdx +++ b/v2/thirdparty/custom-ui/init/user-management-dashboard/setup.mdx @@ -4,329 +4,7 @@ title: "Setting up the dashboard" hide_title: true --- -# Setting up the Dashboard +import Redirector from '/src/components/Redirector'; - - - + -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import AppInfoForm from "/src/components/appInfoForm" -import CustomAdmonition from "/src/components/customAdmonition" - - -## Architecture - - - -Flowchart of architecture when using SuperTokens managed service - - -Flowchart of architecture when self-hosting SuperTokens - - - -The Backend SDK serves the user management dashboard which can be accessed on the `/auth/dashboard` path of your API domain. - - -## Initialise the dashboard recipe - - - -To get started, initialise the Dashboard recipe in the `recipeList`. - - - - -```tsx -import SuperTokens from "supertokens-node"; -import Dashboard from "supertokens-node/recipe/dashboard"; - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // TODO: Initialise other recipes - // highlight-start - Dashboard.init(), - // highlight-end - ], -}); -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/dashboard" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - supertokens.Init(supertokens.TypeInput{ - RecipeList: []supertokens.Recipe{ - // TODO: Initialise other recipes - // highlight-start - dashboard.Init(nil), - // highlight-end - }, - }); -} -``` - - - - -```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import dashboard - -init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore - recipe_list=[ - # TODO: Initialise other recipes - # highlight-start - dashboard.init(), - # highlight-end - ] -) -``` - - - - -## Viewing the dashboard - -:::important -The user management dashboard is served by the backend SDK, you have to use your API domain when trying to visit the dashboard. -::: - -Navigate to `^{form_apiDomain}^{form_apiBasePath}/dashboard` to view the dashboard. - -:::note -If you are using Next.js, upon integrating our backend SDK into your Next.js API folder, the dashboard will be accessible by default at `^{form_apiDomain}/api/auth/dashboard`. For frameworks other than Next.js, it can be accessed at `^{form_apiDomain}/auth/dashboard`. Should you have customized the `apiBasePath` configuration property, simply navigate to `^{form_apiDomain}^{form_apiBasePath}/dashboard` to access the dashboard. -::: - -Dashboard login screen UI - -## Creating dashboard credentials - - - -You can create 3 dashboard users* for free. - -If you need to create additional users: - -- For self hosted users, please [sign up](https://supertokens.com/auth) to generate a license key and follow the instructions sent to you by email. -- For managed service users, you can click on the "enable paid features" button on [our dashboard](https://supertokens.com/dashboard-saas), and follow the steps from there on. - -*: A dashboard user is a user that can log into and view the user management dashboard. These users are independent to the users of your application - - - -When you first setup SuperTokens, there are no credentials created for the dashboard. If you click the "Add a new user" button in the dashboard login screen you can see the command you need to execute in order to create credentials. - -Dashboard signup screen UI - -To create credentials you need to make a request to SuperTokens core. - -- The example above uses the demo core `https://try.supertokens.com`, replace this with the connection uri you pass to the backend SDK when initialising SuperTokens. -- Replace `` with your API key. If you are using a self hosted SuperTokens core there is no API key by default. In that case you can either skip or ignore the `api-key` header. -- Replace `` and `` with the appropriate values. - -:::caution -If using self hosted SuperTokens core, you need to make sure that you add an API key to the core in case it's exposed to the internet. Otherwise anyone will be able to create or modify dashboard users. - -You can add an API key to the core by following the instructions "Auth flow customizations" > "SuperTokens core settings" > "Adding API keys" page. -::: - -## Updating dashboard credentials - -You can update the email or password of existing credentials by using the "Forgot Password" button on the dashboard login page. - -Reset your password screen UI - -To update credentials you need to make a request to SuperTokens core. - -- The example above uses the demo core `https://try.supertokens.com`, replace this with the connection uri you pass to the backend SDK when initialising SuperTokens. -- Replace `` with your API key. If you are using a self hosted SuperTokens core there is no API key by default. In that case you can either skip or ignore the `api-key` header. -- Replace `` and `` with the appropriate values. You can use `newEmail` instead of `newPassword` if you want to update the email - - - - - -## Restricting access to dashboard users - -When using the dashboard recipe you can restrict access to certain features by providing a list of emails to be considered as "admins". When a dashboard user logs in with an email not present in this list, they will only be able to perform read operations and all write operations will result in the backend SDKs failing the request. - -You can provide an array of emails to the backend SDK when initialising the dashboard recipe: - -:::important -- Not providing an admins array will result in all dashboard users being allowed both read and write operations -- Providing an empty array as admins will result in all dashboard users having ONLY read access -::: - - - - -```tsx -import SuperTokens from "supertokens-node"; -import Dashboard from "supertokens-node/recipe/dashboard"; - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // TODO: Initialise other recipes - // highlight-start - Dashboard.init({ - admins: [ - "johndoe@gmail.com", - ], - }), - // highlight-end - ], -}); -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/dashboard" - "github.com/supertokens/supertokens-golang/supertokens" - "github.com/supertokens/supertokens-golang/recipe/dashboard/dashboardmodels" -) - -func main() { - supertokens.Init(supertokens.TypeInput{ - RecipeList: []supertokens.Recipe{ - // TODO: Initialise other recipes - // highlight-start - dashboard.Init(&dashboardmodels.TypeInput{ - Admins: &[]string{ - "johndoe@gmail.com", - }, - }), - // highlight-end - }, - }); -} -``` - - - - -```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import dashboard - -init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore - recipe_list=[ - # TODO: Initialise other recipes - # highlight-start - dashboard.init( - admins=[ - "johndoe@gmail.com", - ], - ), - # highlight-end - ] -) -``` - - - - -## Content Security Policy - - - - -If your backend returns a `Content-Security-Policy` header, you will encounter the following UI displaying the CSP violation details. Follow the instructions provided in this UI to make necessary adjustments to your backend CSP configuration. - -![CSP error handled UI](/img/dashboard/csp-error.png) - - -For example, to address the error message displayed in the above screenshot, you need to modify your `original policy`. In the given example, it appears as follows: - -```text -script-src: - 'self' - 'unsafe-inline' - https://google.com - -img-src: - https://google.com -``` - -To resolve this issue, make the following adjustments: - - -```text -script-src: - 'self' - 'unsafe-inline' - https://google.com - -img-src: - https://google.com - https://cdn.jsdelivr.net/gh/supertokens/ - -``` -Essentially, you need to include the domain listed as the `Blocked URI` in your violated directive block within your original policy. - - - -If you return a `Content-Security-Policy` header in from your backend, you will need to include the following directives for the user management dashboard to work correctly - -```text -script-src: - 'self' - 'unsafe-inline' - https://cdn.jsdelivr.net/gh/supertokens/ -img-src: - https://cdn.jsdelivr.net/gh/supertokens/ - https://purecatamphetamine.github.io/ -``` - - - - -If you return a `Content-Security-Policy` header in from your backend, you will need to include the following directives for the user management dashboard to work correctly - -```text -script-src: - 'self' - 'unsafe-inline' - https://cdn.jsdelivr.net/gh/supertokens/ -img-src: - https://cdn.jsdelivr.net/gh/supertokens/ - https://purecatamphetamine.github.io/ -``` - - - - - \ No newline at end of file diff --git a/v2/thirdparty/custom-ui/init/user-management-dashboard/tenant-management/details.mdx b/v2/thirdparty/custom-ui/init/user-management-dashboard/tenant-management/details.mdx index 4719a57cf..1397ce110 100644 --- a/v2/thirdparty/custom-ui/init/user-management-dashboard/tenant-management/details.mdx +++ b/v2/thirdparty/custom-ui/init/user-management-dashboard/tenant-management/details.mdx @@ -4,65 +4,7 @@ title: "Tenant Details" hide_title: true --- - - - +import Redirector from '/src/components/Redirector'; -# Tenant details + -Upon selection or creation of a tenant, you will be presented with the Tenant Details page. The various sections are explained below. - -Tenant details - -## Tenant ID and users - -The first section shows up the tenant ID and the number of users in that tenant. Clicking on `See Users` takes you to the [user management page](/docs/userdashboard/users-listing-and-details) where the users for the selected tenant can be viewed and managed. - -Tenant users - - -## Enabled Login Methods - -This section displays the various login methods available for the tenant. By enabling these toggles, you can make the corresponding login methods accessible to the users within the tenant. - -Appropriate recipes must be enabled to be able to turn on the login methods. For example, - -- to be able to turn on `emailpassword`, EmailPassword recipe must be initialised in the backend. -- to be able to turn on `OTP Phone`, Passwordless recipe must be initialised with flowType `USER_INPUT_CODE` and contactMethod `PHONE` - -:::info - -If you are using our Auth React SDK, make sure to enable [usesDynamicLoginMethods](/docs/passwordless/common-customizations/multi-tenancy/common-domain-login#step-3-tell-supertokens-about-the-saved-tenantid-from-the-previous-step) so that the frontend can automatically show the login methods based on the selection here. - -::: - -Login Methods - -## Secondary factors - -This section displays the various secondary factors available for the tenant. By enabling these toggles, the corresponding factor will be enabled for all users of the tenant. Refer [Multifactor Authentication docs](/docs/mfa/introduction) for more information. - -[MultiFactorAuth](/docs/mfa/backend-setup) recipe must be initialised to be able to enable Secondary Factors. - -Also, appropriate recipes must be initialised in the backend SDK to be able to use a secondary factor. For example, - -- to be able to turn on TOTP, TOTP recipe must be initialised in the backend. -- to be able to turn on `OTP Phone`, Passwordless recipe must be initialised with flowType `USER_INPUT_CODE` and contactMethod `PHONE` - -Secondary Factors - -## Core config - -Core Config - -This section shows the current config values in core for the tenant. You can edit some of these settings by clicking the `pencil` icon next to the property. - -Edit Core Config - -:::caution - -Some of the config values may not be editable since they are being inherited from the App. If using Supertokens managed hosting, they can be modified in the SaaS Dashboard. Else, if you are self-hosting the SuperTokens core, they will have to be edited via Docker env variables or the config.yaml file. - -::: - - diff --git a/v2/thirdparty/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx b/v2/thirdparty/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx index 32c5447bd..6ae231e40 100644 --- a/v2/thirdparty/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx +++ b/v2/thirdparty/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx @@ -4,29 +4,7 @@ title: "Overview" hide_title: true --- - - - +import Redirector from '/src/components/Redirector'; -# Tenant management overview + -You can now manage [tenants, login methods and third party providers](/docs/multitenancy/introduction) and [Multi factor authentication](/docs/mfa/introduction) of your app from the tenant management dashboard. - -Once the dashboard recipe is initialised, the tenant management should be available without requiring any additional steps. - -:::caution - -Currently, this is only available with our Node and Python SDKs. - -::: - -Tenant Management Landing - - -## Creating a new tenant - -Clicking on `Add Tenant` will prompt you to enter the tenant id. Once the tenant id is entered, click on `Create Now` to create the tenant. You will then be taken to the Tenant Details page where you can further manage the newly created tenant. - -Create Tenant - - diff --git a/v2/thirdparty/custom-ui/init/user-management-dashboard/users-listing-and-details.mdx b/v2/thirdparty/custom-ui/init/user-management-dashboard/users-listing-and-details.mdx index ff5945ed2..7be11467c 100644 --- a/v2/thirdparty/custom-ui/init/user-management-dashboard/users-listing-and-details.mdx +++ b/v2/thirdparty/custom-ui/init/user-management-dashboard/users-listing-and-details.mdx @@ -4,38 +4,7 @@ title: "Viewing user list and details" hide_title: true --- - - - +import Redirector from '/src/components/Redirector'; -# Viewing user list and details + -With the user management dashboard you can view the list of users and their details. You can also perform different operations on theses users as mentioned below. - -## Viewing users list - -If you have just created your app, you may not have any users to show on the dashboard. - -Empty dashboard screen UI - -Navigate to the your frontend app and create a user (via the sign up flow). On creation, if you head back to the dashboard and refresh the page, you will see that user: - -One user in dashboard screen UI - -## User details page - -When you select a user you can view detailed information about the user such as email, phone number, user metadata etc. - -User details page screen UI part one - -User details page screen UI part two - -You can edit user information and perform actions such as resetting a user's password or revoke sessions for a user. - -Change password modal UI - -:::important Note -Some features such as user metadata and email verification have to be enabled in your backend before you can use them in the user management dashboard -::: - - diff --git a/v2/thirdparty/custom-ui/multitenant-login.mdx b/v2/thirdparty/custom-ui/multitenant-login.mdx index 113ce0212..bf2091f6c 100644 --- a/v2/thirdparty/custom-ui/multitenant-login.mdx +++ b/v2/thirdparty/custom-ui/multitenant-login.mdx @@ -4,421 +4,8 @@ title: "Multitenant and Enterprise SSO login" hide_title: true --- -import AppInfoForm from "/src/components/appInfoForm" -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import PythonSyncAsyncSubTabs from "/src/components/tabs/PythonSyncAsyncSubTabs"; -import CoreVersionSubTabs from "/src/components/tabs/CoreVersionSubTabs"; -import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs"; -import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" -import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" -import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" +import Redirector from '/src/components/Redirector'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import MultiTenancyPaidBanner from '../../community/reusableMD/multitenancy/MultiTenancyPaidBanner.mdx' -import CoreInjector from "/src/components/coreInjector" - - - + - - -# Multitenant and Enterprise SSO login - -Multitenant login is a feature that lets you customize the login experience for each of your customers. For example, a customer `customer1` hosted on `customer1.yourdomain.com` can have login with `Active Directory` and `Google`, and another customer `customer2` hosted on `customer2.yourdomain.com` can have login with `Okta` and `Facebook`. - -This is also the page that you should see if you want to implement sign in with: -- Okta (`thirdPartyId: "okta"`) -- SAML (`thirdPartyId: "boxy-saml"`) -- Active Directory (`thirdPartyId: "active-directory"`) -- Google Workspaces (`thirdPartyId: "google-workspaces"`) -- GitLab (`thirdPartyId: "gitlab"`) -- Bitbucket (`thirdPartyId: "bitbucket"`) -- Or any other workforce IdP - - - - - - - -## Step 1: Create and configure a new tenant in SuperTokens core - -Each tenant can be configured with a unique `tenantId`, and the list of third party connections (Active Directory, Google etc..) that should be allowed for them. - -You can create a new tenant using our backend SDKs or via a `cURL` command to the core. - - - - - - - - - - - -```tsx -import Multiteancy from "supertokens-node/recipe/multitenancy"; - -async function createTenant() { - let resp = await Multiteancy.createOrUpdateTenant("customer1", { - firstFactors: ["thirdparty"] - }); - - if (resp.createdNew) { - // new tenant was created - } else { - // existing tenant's config was modified. - } -} -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/multitenancy" - "github.com/supertokens/supertokens-golang/recipe/multitenancy/multitenancymodels" -) - -func main() { - tenantId := "customer1" - thirdPartyEnabled := true - - // highlight-start - resp, err := multitenancy.CreateOrUpdateTenant(tenantId, multitenancymodels.TenantConfig{ - ThirdPartyEnabled: &thirdPartyEnabled, - }) - // highlight-end - - if err != nil { - // handle error - } - if resp.OK.CreatedNew { - // new tenant was created - } else { - // existing tenant's config was modified. - } -} -``` - - - - - - - -```python -from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate - -async def some_func(): - result = await create_or_update_tenant( - "public", - TenantConfigCreateOrUpdate( - first_factors=["thirdparty"], - ), - ) - - if result.status != "OK": - print("handle error") - elif result.created_new: - print("new tenant was created") - else: - print("existing tenant's config was modified.") -``` - - - - - -```python -from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate - -def some_func(): - result = create_or_update_tenant( - "public", - TenantConfigCreateOrUpdate( - first_factors=["thirdparty"], - ), - ) - - if result.status != "OK": - print("handle error") - elif result.created_new: - print("new tenant was created") - else: - print("existing tenant's config was modified.") -``` - - - - - - - - - - - - - - -```bash -curl --location --request PUT '^{coreInjector_uri_without_quotes}/recipe/multitenancy/tenant/v2' \ ---header 'api-key: ^{coreInjector_api_key_without_quotes}' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "tenantId": "customer1", - "firstFactors": ["thirdparty"] -}' -``` - - - - - -```bash -curl --location --request PUT '^{coreInjector_uri_without_quotes}/recipe/multitenancy/tenant' \ ---header 'api-key: ^{coreInjector_api_key_without_quotes}' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "tenantId": "customer1", - "thirdPartyEnabled": true -}' -``` - - - - - - - - - - - -Create Tenant - - - - - - - - - - - -Once a tenant is created, add their thirdparty providers as shown below. - - - - - - - - - - - -```tsx -import Multiteancy from "supertokens-node/recipe/multitenancy"; - -async function createTenant() { - let resp = await Multiteancy.createOrUpdateThirdPartyConfig("customer1", { - thirdPartyId: "active-directory", - name: "Active Directory", - clients: [{ - clientId: "...", - clientSecret: "...", - }], - oidcDiscoveryEndpoint: "https://login.microsoftonline.com//v2.0/.well-known/openid-configuration", - }); - - if (resp.createdNew) { - // Active Directory added to customer1 - } else { - // Existing active directory config overwritten for customer1 - } -} -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/multitenancy" - "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" -) - -func main() { - tenantId := "customer1" - - // highlight-start - resp, err := multitenancy.CreateOrUpdateThirdPartyConfig(tenantId, tpmodels.ProviderConfig{ - ThirdPartyId: "active-directory", - Name: "Active Directory", - Clients: []tpmodels.ProviderClientConfig{ - { - ClientID: "...", - ClientSecret: "...", - }, - }, - OIDCDiscoveryEndpoint: "https://login.microsoftonline.com//v2.0/.well-known/openid-configuration", - }, nil, nil) - // highlight-end - - if err != nil { - // handle error - } - if resp.OK.CreatedNew { - // Active Directory added to customer1 - } else { - // Existing active directory config overwritten for customer1 - } -} -``` - - - - - - - -```python -from supertokens_python.recipe.multitenancy.asyncio import create_or_update_third_party_config -from supertokens_python.recipe.thirdparty.provider import ProviderConfig, ProviderClientConfig - -async def update_tenant(): - result = await create_or_update_third_party_config( - "customer1", - config=ProviderConfig( - third_party_id="active-directory", - name="Active Directory", - clients=[ - ProviderClientConfig( - client_id="...", - client_secret="...", - ) - ], - oidc_discovery_endpoint="https://login.microsoftonline.com//v2.0/.well-known/openid-configuration", - ), - ) - - if result.status != "OK": - print("Error adding active directory to tenant") - elif result.created_new: - print("Active directory was added to the tenant") - else: - print("Existing tenant's active directory config was modified") -``` - - - - - -```python -from supertokens_python.recipe.multitenancy.syncio import create_or_update_third_party_config -from supertokens_python.recipe.thirdparty.provider import ProviderConfig, ProviderClientConfig - -result = create_or_update_third_party_config( - "customer1", - config=ProviderConfig( - third_party_id="active-directory", - name="Active Directory", - clients=[ - ProviderClientConfig( - client_id="...", - client_secret="...", - ) - ] - ), - ) - -if result.status != "OK": - print("Error creating or updating tenant") -elif result.created_new: - print("New tenant was created") -else: - print("Existing tenant's config was modified") -``` - - - - - - - - - - -```bash -curl --location --request PUT '^{coreInjector_uri_without_quotes}//recipe/multitenancy/config/thirdparty' \ ---header 'api-key: ^{coreInjector_api_key_without_quotes}' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "config": { - "thirdPartyId": "active-directory", - "name": "Active Directory", - "clients": [ - { - "clientId": "...", - "clientSecret": "..." - } - ], - "oidcDiscoveryEndpoint": "https://login.microsoftonline.com//v2.0/.well-known/openid-configuration" - } -}' -``` - - - - - - - -Create Third Party Provider - - - - - - - - - - - -:::important -The above shows how to add an Active Directory config for the `customer1` tenant. You can see the config structure for all the in built providers [on this page](../common-customizations/sign-in-and-up/provider-config). -::: - - - - - - - -## Step 2: Build your multi tenant a UX flow - -The most common multi tenant flows are: -- [Tenants use a common domain to login](../common-customizations/multi-tenancy/common-domain-login): All tenants login using the same page (for example, `example.com/auth`) and are optionally redirected to their sub domain post login. At the start of the login flow, the customer will have to input their tenantId / workspace URL / identifier - as defined by you, and the login methods shown would be based on their tenantId. -- [Tenants use their sub domain to login](../common-customizations/multi-tenancy/sub-domain-login): Here, each tenant has a sub domain assigned to them (for example `customer1.example.com`, `customer2.example.com`, ...), and they would visit their sub domain to login and access their app. Each sub domain's login experience may be different (as defined by you or the tenant). - -SuperTokens is flexible enough to allow other forms of UX as well, but since the above two flow are most common, we provide dedicated docs for them (see the links above). - -## See also - -- Multi tenant [single domain](../common-customizations/multi-tenancy/common-domain-login) or [sub domain](../common-customizations/multi-tenancy/sub-domain-login) login. -- [See all built in multitenant providers](../common-customizations/sign-in-and-up/provider-config). -- [See how to add custom multitenant provider](../common-customizations/multi-tenancy/custom-provider). -- [SAML login](../common-customizations/saml/what-is-saml). -- [Tenant data isolation](../common-customizations/multi-tenancy/new-tenant-config#providing-additional-configuration-per-tenant). - - diff --git a/v2/thirdparty/custom-ui/securing-routes.mdx b/v2/thirdparty/custom-ui/securing-routes.mdx index 665ebb50b..adf124a95 100644 --- a/v2/thirdparty/custom-ui/securing-routes.mdx +++ b/v2/thirdparty/custom-ui/securing-routes.mdx @@ -4,590 +4,8 @@ title: "Securing your API and frontend routes" hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -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" -import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" -import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" -import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" + -# Securing your API and frontend routes - -## Protecting APIs - - - - - - - -### Requiring an active session - -For your APIs that require a user to be logged in, use the `verifySession` middleware - - - - - - -```tsx -import express from "express"; -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import { SessionRequest } from "supertokens-node/framework/express"; - -let app = express(); - -// highlight-start -app.post("/like-comment", verifySession(), (req: SessionRequest, res) => { - let userId = req.session!.getUserId(); - // highlight-end - //.... -}); -``` - - - - -```tsx -import Hapi from "@hapi/hapi"; -import { verifySession } from "supertokens-node/recipe/session/framework/hapi"; -import { SessionRequest } from "supertokens-node/framework/hapi"; - -let server = Hapi.server({ port: 8000 }); - -server.route({ - path: "/like-comment", - method: "post", - //highlight-start - options: { - pre: [ - { - method: verifySession() - }, - ], - }, - handler: async (req: SessionRequest, res) => { - let userId = req.session!.getUserId(); - //highlight-end - //... - } -}) -``` - - - -```tsx -import Fastify from "fastify"; -import { verifySession } from "supertokens-node/recipe/session/framework/fastify"; -import { SessionRequest } from "supertokens-node/framework/fastify"; - -let fastify = Fastify(); - -//highlight-start -fastify.post("/like-comment", { - preHandler: verifySession(), -}, (req: SessionRequest, res) => { - let userId = req.session!.getUserId(); - //highlight-end - //.... -}); -``` - - - - -```tsx -import { verifySession } from "supertokens-node/recipe/session/framework/awsLambda"; -import { SessionEventV2 } from "supertokens-node/framework/awsLambda"; - -async function likeComment(awsEvent: SessionEventV2) { - let userId = awsEvent.session!.getUserId(); - //.... -}; - -//highlight-next-line -exports.handler = verifySession(likeComment); -``` - - - - -```tsx -import KoaRouter from "koa-router"; -import { verifySession } from "supertokens-node/recipe/session/framework/koa"; -import { SessionContext } from "supertokens-node/framework/koa"; - -let router = new KoaRouter(); - -//highlight-start -router.post("/like-comment", verifySession(), (ctx: SessionContext, next) => { - let userId = ctx.session!.getUserId(); - //highlight-end - //.... -}); -``` - - - - -```tsx -import { inject, intercept } from "@loopback/core"; -import { RestBindings, MiddlewareContext, post, response } from "@loopback/rest"; -import { verifySession } from "supertokens-node/recipe/session/framework/loopback"; -import { SessionContext } from "supertokens-node/framework/loopback"; - -class LikeComment { - //highlight-start - constructor(@inject(RestBindings.Http.CONTEXT) private ctx: MiddlewareContext) { } - @post("/like-comment") - @intercept(verifySession()) - @response(200) - handler() { - let userId = (this.ctx as SessionContext).session!.getUserId(); - //highlight-end - //.... - } -} -``` - - - - -```tsx -import { superTokensNextWrapper } from 'supertokens-node/nextjs' -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import { SessionRequest } from "supertokens-node/framework/express"; - -// highlight-start -export default async function likeComment(req: SessionRequest, res: any) { - await superTokensNextWrapper( - async (next) => { - await verifySession()(req, res, next); - }, - req, - res - ) - - let userId = req.session!.getUserId(); - // highlight-end - //.... -} -``` - - - - -```tsx -import { NextResponse, NextRequest } from "next/server"; -import SuperTokens from "supertokens-node"; -import { withSession } from "supertokens-node/nextjs"; -// @ts-ignore -import { backendConfig } from "@/app/config/backend"; - -SuperTokens.init(backendConfig()); - -export function POST(request: NextRequest) { - return withSession(request, async (err, session) => { - if (err) { - return NextResponse.json(err, { status: 500 }); - } - const userId = session!.getUserId(); - //.... - return NextResponse.json({}); - }); -} -``` - - - - -```tsx -import { Controller, Post, UseGuards, Session } from "@nestjs/common"; -import { SessionContainer } from "supertokens-node/recipe/session"; -// @ts-ignore -import { AuthGuard } from './auth/auth.guard'; - -@Controller() -export class ExampleController { - @Post('example') - @UseGuards(new AuthGuard()) // For more information about this guard please read our NestJS guide. - async postExample(@Session() session: SessionContainer): Promise { - //highlight-start - let userId = session.getUserId(); - - //highlight-end - //.... - return true; - } -} -``` - - - - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/supertokens/supertokens-golang/recipe/session" -) - -func main() { - _ = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - // Wrap the API handler in session.VerifySession - session.VerifySession(nil, likeCommentAPI).ServeHTTP(rw, r) - }) -} - -func likeCommentAPI(w http.ResponseWriter, r *http.Request) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(r.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/gin-gonic/gin" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" -) - -func main() { - router := gin.New() - - // Wrap the API handler in session.VerifySession - router.POST("/likecomment", verifySession(nil), likeCommentAPI) -} - -// This is a function that wraps the supertokens verification function -// to work the gin -func verifySession(options *sessmodels.VerifySessionOptions) gin.HandlerFunc { - return func(c *gin.Context) { - session.VerifySession(options, func(rw http.ResponseWriter, r *http.Request) { - c.Request = c.Request.WithContext(r.Context()) - c.Next() - })(c.Writer, c.Request) - // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly - c.Abort() - } -} - -func likeCommentAPI(c *gin.Context) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(c.Request.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/go-chi/chi" - "github.com/supertokens/supertokens-golang/recipe/session" -) - -func main() { - r := chi.NewRouter() - - // Wrap the API handler in session.VerifySession - r.Post("/likecomment", session.VerifySession(nil, likeCommentAPI)) -} - -func likeCommentAPI(w http.ResponseWriter, r *http.Request) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(r.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/gorilla/mux" - "github.com/supertokens/supertokens-golang/recipe/session" -) - -func main() { - router := mux.NewRouter() - - // Wrap the API handler in session.VerifySession - router.HandleFunc("/likecomment", session.VerifySession(nil, likeCommentAPI)).Methods(http.MethodPost) -} - -func likeCommentAPI(w http.ResponseWriter, r *http.Request) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(r.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - - - - - - -```python -from supertokens_python.recipe.session.framework.fastapi import verify_session -from supertokens_python.recipe.session import SessionContainer -from fastapi import Depends - -# highlight-start -@app.post('/like_comment') # type: ignore -async def like_comment(session: SessionContainer = Depends(verify_session())): - user_id = session.get_user_id() - # highlight-end - - print(user_id) -``` - - - - -```python -from supertokens_python.recipe.session.framework.flask import verify_session -from supertokens_python.recipe.session import SessionContainer -from flask import g - -# highlight-start -@app.route('/update-jwt', methods=['POST']) # type: ignore -@verify_session() -def like_comment(): - session: SessionContainer = g.supertokens # type: ignore - - user_id = session.get_user_id() - # highlight-end - - print(user_id) -``` - - - - -```python -from supertokens_python.recipe.session.framework.django.asyncio import verify_session -from django.http import HttpRequest -from supertokens_python.recipe.session import SessionContainer - -# highlight-start -@verify_session() -async def like_comment(request: HttpRequest): - session: SessionContainer = request.supertokens # type: ignore - - user_id = session.get_user_id() - # highlight-end - - print(user_id) -``` - - - - - - - -The `verifySession` function returns a `401` to the frontend if a session doesn't exist, or if the access token has expired, in which case, our frontend SDK automatically refreshes the session. - -In case of successful session verification, you get access to a `session` object using which you can get the user's ID, or manipulate the session information. - - - - - - - -### Microservice authentication - -For authentication between microservices on your backend, checkout the [microservice auth guide](/docs/microservice_auth/client-credentials). - - - -## Protecting frontend routes - - - - - - - - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```tsx -import Session from 'supertokens-web-js/recipe/session'; - -async function doesSessionExist() { - if (await Session.doesSessionExist()) { - // user is logged in - } else { - // user has not logged in yet - } -} -``` - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```tsx -import Session from 'supertokens-web-js-script/recipe/session'; -async function doesSessionExist() { - if (await Session.doesSessionExist()) { - // user is logged in - } else { - // user has not logged in yet - } -} -``` - - - - - - - - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```tsx -import SuperTokens from 'supertokens-react-native'; - -async function doesSessionExist() { - if (await SuperTokens.doesSessionExist()) { - // user is logged in - } else { - // user has not logged in yet - } -} -``` - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```kotlin -import android.app.Application -import com.supertokens.session.SuperTokens - -class MainApplication: Application() { - fun doesSessionExist() { - if (SuperTokens.doesSessionExist(this.applicationContext)) { - // user is logged in - } else { - // user has not logged in yet - } - } -} -``` - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```swift -import UIKit -import SuperTokensIOS - -fileprivate class ViewController: UIViewController { - func doesSessionExist() { - if SuperTokens.doesSessionExist() { - // User is logged in - } else { - // User is not logged in - } - } -} -``` - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```dart -import 'package:supertokens_flutter/supertokens.dart'; - -Future doesSessionExist() async { - return await SuperTokens.doesSessionExist(); -} -``` - - - - - - - - - - - - - - - -## See also - -- Optional sessions [for APIs](../common-customizations/sessions/session-verification-in-api/verify-session#optional-session-verification) and [the frontend](../common-customizations/sessions/checking-session-front-end) -- [Verifying session without using a middleware](../common-customizations/sessions/session-verification-in-api/get-session) -- Session claim validation [for APIs](../common-customizations/sessions/claims/claim-validators) and [the frontend](../common-customizations/sessions/claims/claim-validators) -- [Changing session lifetime](../common-customizations/sessions/change-session-timeout) -- [Sharing session across sub domains](../common-customizations/sessions/share-sessions-across-sub-domains) - - diff --git a/v2/thirdparty/custom-ui/sign-out.mdx b/v2/thirdparty/custom-ui/sign-out.mdx index af9169fa0..81b1a86e9 100644 --- a/v2/thirdparty/custom-ui/sign-out.mdx +++ b/v2/thirdparty/custom-ui/sign-out.mdx @@ -4,135 +4,9 @@ title: Implementing sign out hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" -import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" -import TabItem from '@theme/TabItem'; -import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" -# Implementing sign out + -The `signOut` method revokes the session on the frontend and on the backend. Calling this function without a valid session also yields a successful response. - - - - - - -```tsx -import Session from "supertokens-web-js/recipe/session"; - -async function logout () { - // highlight-next-line - await Session.signOut(); - window.location.href = "/auth"; // or to wherever your logic page is -} -``` - - - - -```tsx -import supertokensSession from "supertokens-web-js-script/recipe/session"; -async function logout () { - // highlight-next-line - await supertokensSession.signOut(); - window.location.href = "/auth"; // or to wherever your logic page is -} -``` - - - - - - - - - - - - -```tsx -import SuperTokens from "supertokens-react-native"; - -async function logout () { - // highlight-next-line - await SuperTokens.signOut(); - // navigate to the login screen.. -} -``` - - - - - -```kotlin -import android.app.Application -import com.supertokens.session.SuperTokens - -class MainApplication: Application() { - fun logout() { - // highlight-next-line - SuperTokens.signOut(this); - // navigate to the login screen.. - } -} -``` - - - - - -```swift -import UIKit -import SuperTokensIOS - -fileprivate class ViewController: UIViewController { - func signOut() { - SuperTokens.signOut(completionHandler: { - error in - - if error != nil { - // handle error - } else { - // Signed out successfully - } - }) - } -} -``` - - - - - -```dart -import 'package:supertokens_flutter/supertokens.dart'; - -Future signOut() async { - await SuperTokens.signOut( - completionHandler: (error) { - // handle error if any - } - ); -} -``` - - - - - - - - - -- On success, the `signOut` function does not redirect the user to another page, so you must redirect the user yourself. -- The `signOut` function calls the signout API exposed by the session recipe on the backend. -- If you call the `signOut` function whilst the access token has expired, but the refresh token still exists, our SDKs will do an automatic session refresh before revoking the session. - -## See also - -- [Revoking a session on the backend](../common-customizations/sessions/revoke-session) diff --git a/v2/thirdparty/custom-ui/thirdparty-login.mdx b/v2/thirdparty/custom-ui/thirdparty-login.mdx index 9e98ad4a4..903281af2 100644 --- a/v2/thirdparty/custom-ui/thirdparty-login.mdx +++ b/v2/thirdparty/custom-ui/thirdparty-login.mdx @@ -4,1176 +4,9 @@ title: Social login hide_title: true --- - - - +import Redirector from '/src/components/Redirector'; -import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" -import TabItem from '@theme/TabItem'; -import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" -import AppInfoForm from "/src/components/appInfoForm" -import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -# Social login + -There are different flows that the third party login provider may support: -- _**Flow 1)**_ Via authorisation code (one time use code) - for web and mobile apps. - - _**a)**_ If you have configred the client secret on the backend: - - The frontend sends the auth code to the backend, the backend exchanges that with the provided client secret to get the access token. The access token is then used to fetch user info and log them in. - - _**b)**_ If you have **not** provided the client secret on the backend: - - The backend uses PKCE flow to exchange the auth code with the user's access token. The access token is then used to fetch user info and log them in. -- _**Flow 2)**_ Via OAuth / access tokens - for mobile apps. - - The access token is available on the frontend and is sent to the backend. SuperTokens then fetches user info using the access token and logs them in. -:::note -The same flow applies during sign up and sign in. If the user is signing up, the `createdNewUser` boolean on the frontend and backend will be `true` (as the result of the sign in up API call). -::: - - - - -

      Flow 1a: Authorization code grant flow (Sign in with Google example)

      - -

      Step 1) Redirecting to social / SSO provider

      - -The first step is to fetch the URL on which the user will be authenticated. This can be done by querying the backend API exposed by SuperTokens (as shown below). The backend SDK automatically appends the right query params to the URL (like scope, client ID etc). - -After we get the URL, we simply redirect the user there. In the code below, we will take an example of login with Google: - - - - -```tsx -import { getAuthorisationURLWithQueryParamsAndSetState } from "supertokens-web-js/recipe/thirdparty"; - -async function googleSignInClicked() { - try { - const authUrl = await getAuthorisationURLWithQueryParamsAndSetState({ - thirdPartyId: "google", - - // This is where Google should redirect the user back after login or error. - // This URL goes on the Google's dashboard as well. - frontendRedirectURI: "http:///auth/callback/google", - }); - - /* - Example value of authUrl: https://accounts.google.com/o/oauth2/v2/auth/oauthchooseaccount?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email&access_type=offline&include_granted_scopes=true&response_type=code&client_id=1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com&state=5a489996a28cafc83ddff&redirect_uri=https%3A%2F%2Fsupertokens.io%2Fdev%2Foauth%2Fredirect-to-app&flowName=GeneralOAuthFlow - */ - - // we redirect the user to google for auth. - window.location.assign(authUrl); - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - -```tsx -import supertokensThirdParty from "supertokens-web-js-script/recipe/thirdparty"; -async function googleSignInClicked() { - try { - const authUrl = await supertokensThirdParty.getAuthorisationURLWithQueryParamsAndSetState({ - thirdPartyId: "google", - - // This is where Google should redirect the user back after login or error. - // This URL goes on the Google's dashboard as well. - frontendRedirectURI: "http:///auth/callback/google", - }); - - /* - Example value of authUrl: https://accounts.google.com/o/oauth2/v2/auth/oauthchooseaccount?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email&access_type=offline&include_granted_scopes=true&response_type=code&client_id=1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com&state=5a489996a28cafc83ddff&redirect_uri=https%3A%2F%2Fsupertokens.io%2Fdev%2Foauth%2Fredirect-to-app&flowName=GeneralOAuthFlow - */ - - // we redirect the user to google for auth. - window.location.assign(authUrl); - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - -

      Step 2) Handling the auth callback on your frontend

      - -Once the third party provider redirects your user back to your app, you need to consume the information to sign in the user. This requires you to: -- Setup a route in your app that will handle this callback. We recommend something like `http:///auth/callback/google` (for Google). Regardless of what you make this path, remember to use that same path when calling the `getAuthorisationURLWithQueryParamsAndSetState` function in the first step. - -- On that route, call the following function on page load - - - - - ```tsx - import { signInAndUp } from "supertokens-web-js/recipe/thirdparty"; - - async function handleGoogleCallback() { - try { - const response = await signInAndUp(); - - if (response.status === "OK") { - console.log(response.user) - if (response.createdNewRecipeUser && response.user.loginMethods.length === 1) { - // sign up successful - } else { - // sign in successful - } - window.location.assign("/home"); - } else if (response.status === "SIGN_IN_UP_NOT_ALLOWED") { - // the reason string is a user friendly message - // about what went wrong. It can also contain a support code which users - // can tell you so you know why their sign in / up was not allowed. - window.alert(response.reason) - } else { - // SuperTokens requires that the third party provider - // gives an email for the user. If that's not the case, sign up / in - // will fail. - - // As a hack to solve this, you can override the backend functions to create a fake email for the user. - - window.alert("No email provided by social login. Please use another form of login"); - window.location.assign("/auth"); // redirect back to login page - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } - } - ``` - - - - - ```tsx - import supertokensThirdParty from "supertokens-web-js-script/recipe/thirdparty"; - async function handleGoogleCallback() { - try { - const response = await supertokensThirdParty.signInAndUp(); - - if (response.status === "OK") { - console.log(response.user) - if (response.createdNewRecipeUser && response.user.loginMethods.length === 1) { - // sign up successful - } else { - // sign in successful - } - window.location.assign("/home"); - } else if (response.status === "SIGN_IN_UP_NOT_ALLOWED") { - // the reason string is a user friendly message - // about what went wrong. It can also contain a support code which users - // can tell you so you know why their sign in / up was not allowed. - window.alert(response.reason) - } else { - // SuperTokens requires that the third party provider - // gives an email for the user. If that's not the case, sign up / in - // will fail. - - // As a hack to solve this, you can override the backend functions to create a fake email for the user. - - window.alert("No email provided by social login. Please use another form of login"); - window.location.assign("/auth"); // redirect back to login page - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } - } - ``` - - - - -:::note -On success, the backend will send back session tokens as part of the response headers which will be automatically handled by our frontend SDK for you. -::: - -

      Special case for login with Apple

      - - - -Unlike other providers, Apple will not redirect your user back to your frontend app. Instead, it will redirect the user to your backend with a `FORM POST` request. This means that the URL that you configure on the Apple's dashboard should point to your backend API layer in which **our middleware** will handle the request and redirect the user to your frontend app. Your frontend app should then call the `signInAndUp` API on that page as shown previously. - -In order to tell SuperTokens which frontend route to redirect the user back to, you need to set the `frontendRedirectURI` to the frontend route (just like for other providers), and also need to set the `redirectURIOnProviderDashboard` to point to your backend API route (to which Apple will send a POST request). - - - - -```tsx -import { getAuthorisationURLWithQueryParamsAndSetState } from "supertokens-web-js/recipe/thirdparty"; - -async function appleSignInClicked() { - try { - const authUrl = await getAuthorisationURLWithQueryParamsAndSetState({ - thirdPartyId: "apple", - - frontendRedirectURI: "http://localhost:3000/auth/callback/apple", // This is an example callback URL on your frontend. You can use another path as well. - // highlight-start - redirectURIOnProviderDashboard: "^{form_apiDomain}^{form_apiBasePath}/callback/apple", // This URL goes on the Apple's dashboard - // highlight-end - }); - - // we redirect the user to apple for auth. - window.location.assign(authUrl); - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - -```tsx -import supertokensThirdParty from "supertokens-web-js-script/recipe/thirdparty"; -async function appleSignInClicked() { - try { - const authUrl = await supertokensThirdParty.getAuthorisationURLWithQueryParamsAndSetState({ - thirdPartyId: "apple", - - frontendRedirectURI: "http://localhost:3000/auth/callback/apple", // This is an example callback URL on your frontend. You can use another path as well. - // highlight-start - redirectURIOnProviderDashboard: "^{form_apiDomain}^{form_apiBasePath}/callback/apple", // This URL goes on the Apple's dashboard - // highlight-end - }); - - /* - Example value of authUrl: https://accounts.google.com/o/oauth2/v2/auth/oauthchooseaccount?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email&access_type=offline&include_granted_scopes=true&response_type=code&client_id=1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com&state=5a489996a28cafc83ddff&redirect_uri=https%3A%2F%2Fsupertokens.io%2Fdev%2Foauth%2Fredirect-to-app&flowName=GeneralOAuthFlow - */ - - // we redirect the user to google for auth. - window.location.assign(authUrl); - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - - - -

      Flow 1b: Authorization code grant flow with PKCE

      - -This is identical to flow 1a, except that you do **not** need to provide a client secret during backend init. This flow only works for providers which support the [PKCE flow](https://oauth.net/2/pkce/). - -

      Flow 2: Via OAuth / Access token

      - -:::caution -This flow is not applicable for web apps. -::: - -
      - - - -

      Flow 1a: Authorization code grant flow

      - -

      Sign in with Apple example

      - - - - - -

      Step 1) Fetching the authorisation token on the frontend

      - -For react native apps, this involves setting up the [react-native-apple-authentication library](https://github.com/invertase/react-native-apple-authentication) in your app. Checkout their `README` for steps on how to integrate their SDK into your application. The minimum scope required by SuperTokens is the one that gives the user's email (In case of Apple, that could be the user's actual email or the proxy email provided by Apple - it doesn't really matter). - -Once the integration is done, you should call the `appleAuth.performRequest` function for iOS and the `appleAuthAndroid.signIn` function for Android. Either way, the result of the function will be a one time use auth code which you should send to your backend as shown in the next step. - -A full example of this can be found in [our example app](https://github.com/supertokens/supertokens-react-native/blob/master/examples/with-thirdparty/apple.ts). - -In case you are using Expo, you can use the [expo-apple-authentication](https://docs.expo.dev/versions/latest/sdk/apple-authentication/) library instead (not that this library only works on iOS). - -
      - - - -

      Step 1) Fetching the authorisation token on the frontend

      - -:::info -Coming Soon -::: - -
      - - - -

      Step 1) Fetching the authorisation token on the frontend

      - -For iOS you use the normal sign in with apple flow and then use the authorization code to login with SuperTokens. You can see a full example of this in the `onAppleClicked` function in [our example app](https://github.com/supertokens/supertokens-ios/blob/master/examples/with-thirdparty/with-thirdparty/LoginScreen/LoginScreenViewController.swift). - -```swift -import UIKit -import AuthenticationServices - -fileprivate class ViewController: UIViewController, ASAuthorizationControllerPresentationContextProviding, ASAuthorizationControllerDelegate { - func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor { - return view.window! - } - - func loginWithApple() { - let authorizationRequest = ASAuthorizationAppleIDProvider().createRequest() - authorizationRequest.requestedScopes = [.email, .fullName] - - let authorizationController = ASAuthorizationController(authorizationRequests: [authorizationRequest]) - - authorizationController.presentationContextProvider = self - authorizationController.delegate = self - authorizationController.performRequests() - } - - func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { - guard let credential: ASAuthorizationAppleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential, - let authorizationCode = credential.authorizationCode, - let authorizationCodeString: String = String(data: authorizationCode, encoding: .utf8), - let email: String = credential.email as? String, - let nameComponents: PersonNameComponents = credential.fullName as? PersonNameComponents, - let firstName: String = nameComponents.givenName as? String, - let lastName: String = nameComponents.familyName as? String else {return} - - // Send the user information and auth code to the backend. Refer to the next step. - } -} -``` - -
      - - - -

      Step 1) Fetching the authorisation token on the frontend

      - -For flutter we use the [sign_in_with_apple](https://pub.dev/packages/sign_in_with_apple) package, make sure to follow the prerequisites steps to get the package setup. After setup use the snippet below to trigger the apple sign in flow. You can see a full example of this in the `loginWithApple` function in [our example app](https://github.com/supertokens/supertokens-flutter/blob/master/examples/with-thirdparty/lib/screens/login.dart). - -```dart -import 'package:sign_in_with_apple/sign_in_with_apple.dart'; - -void loginWithApple() async { - try { - var credential = await SignInWithApple.getAppleIDCredential( - scopes: [ - AppleIDAuthorizationScopes.email, - AppleIDAuthorizationScopes.fullName, - ], - // Required for Android only - webAuthenticationOptions: WebAuthenticationOptions( - clientId: "", - redirectUri: Uri.parse( - "//callback/apple", - ), - ), - ); - - String authorizationCode = credential.authorizationCode; - String? idToken = credential.identityToken; - String? email = credential.email; - String? firstname = credential.givenName; - String? lastName = credential.familyName; - - // Send the user information and auth code to the backend. Refer to the next step. - } catch (e) { - // Sign in aborted or failed - } -} -``` - -In the snippet above for Android we need to pass an additional `webAuthenticationOptions` property when signing in with Apple. This is because on Android the library uses the web login flow and we need to provide it with the client id and redirection uri. The `redirectUri` property here is the URL to which Apple will make a `POST` request after the user has logged in. The SuperTokens backend SDKs provide an API for this at `//callback/apple`. - -

      Additional steps for Android

      - -For android we also need to provide a way for the web login flow to redirect back to the app. By default the API provided by the backend SDKs redirect to the website domain you provide when initialising the SDK, we can override the API to have it redirect to our app instead. For example if you were using the Node.js SDK: - - - - - -```tsx -import ThirdParty from "supertokens-node/recipe/thirdparty"; - -ThirdParty.init({ - // highlight-start - override: { - apis: (original) => { - return { - ...original, - appleRedirectHandlerPOST: async (input) => { - if (original.appleRedirectHandlerPOST === undefined) { - throw Error("Should never come here"); - } - - // inut.formPostInfoFromProvider contains all the query params attached by Apple - const stateInBase64 = input.formPostInfoFromProvider.state; - - // The web SDKs add a default state - if (stateInBase64 === undefined) { - // Redirect to android app - // We create a dummy URL to create the query string - const dummyUrl = new URL("http://localhost:8080"); - for (const [key, value] of Object.entries(input.formPostInfoFromProvider)) { - dummyUrl.searchParams.set(key, `${value}`); - } - - const queryString = dummyUrl.searchParams.toString(); - // Refer to the README of sign_in_with_apple to understand what this url is - const redirectUrl = `intent://callback?${queryString}#Intent;package=YOUR.PACKAGE.IDENTIFIER;scheme=signinwithapple;end`; - - input.options.res.setHeader("Location", redirectUrl, false); - input.options.res.setStatusCode(303); - input.options.res.sendHTMLResponse(""); - } else { - // For the web flow we can use the original implementation - original.appleRedirectHandlerPOST(input); - } - }, - }; - }, - }, - // highlight-end -}) -``` - - - - - -```go -import ( - "net/http" - "strings" - - "github.com/supertokens/supertokens-golang/recipe/thirdparty" - "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" -) - -func main() { - thirdparty.Init(&tpmodels.TypeInput{ - Override: &tpmodels.OverrideStruct{ - // highlight-start - APIs: func(originalImplementation tpmodels.APIInterface) tpmodels.APIInterface { - originalAppleRedirectPost := *originalImplementation.AppleRedirectHandlerPOST - - *originalImplementation.AppleRedirectHandlerPOST = func(formPostInfoFromProvider map[string]interface{}, options tpmodels.APIOptions, userContext *map[string]interface{}) error { - // formPostInfoFromProvider contains all the query params attached by Apple - state, stateOk := formPostInfoFromProvider["state"].(string) - - queryParams := []string{} - if (!stateOk) || state == "" { - // Redirect to android app - for key, value := range formPostInfoFromProvider { - queryParams = append(queryParams, key+"="+value.(string)) - } - - queryString := "" - if len(queryParams) > 0 { - queryString = strings.Join(queryParams, "&") - } - - // Refer to the README of sign_in_with_apple to understand what this url is - redirectUri := "intent://callback?" + queryString + "#Intent;package=YOUR.PACKAGE.IDENTIFIER;scheme=signinwithapple;end" - - options.Res.Header().Set("Location", redirectUri) - options.Res.WriteHeader(http.StatusSeeOther) - return nil - } else { - return originalAppleRedirectPost(formPostInfoFromProvider, options, userContext) - } - } - - return originalImplementation - }, - }, - // highlight-end - }) -} -``` - - - - - -```python -from supertokens_python.recipe import thirdparty -from supertokens_python.recipe.thirdparty.interfaces import APIInterface, APIOptions -from typing import Dict, Any - -# highlight-start -def override_thirdparty_apis(original_implementation: APIInterface): - original_apple_redirect_post = original_implementation.apple_redirect_handler_post - - async def apple_redirect_handler_post( - form_post_info: Dict[str, Any], - api_options: APIOptions, - user_context: Dict[str, Any] - ): - # form_post_info contains all the query params attached by Apple - state = form_post_info["state"] - - # The web SDKs add a default state - if state is None: - query_items = [ - f"{key}={value}" for key, value in form_post_info.items() - ] - - query_string = "&".join(query_items) - - # Refer to the README of sign_in_with_apple to understand what this url is - redirect_url = "intent://callback?" + query_string + "#Intent;package=YOUR.PACKAGE.IDENTIFIER;scheme=signinwithapple;end" - - api_options.response.set_header("Location", redirect_url) - api_options.response.set_status_code(303) - api_options.response.set_html_content("") - else: - return await original_apple_redirect_post(form_post_info, api_options, user_context) - - original_implementation.apple_redirect_handler_post = apple_redirect_handler_post - return original_implementation -# highlight-end - -thirdparty.init( - # highlight-start - override=thirdparty.InputOverrideConfig( - apis=override_thirdparty_apis - ), - # highlight-end -) -``` - - - - - -In the code above we override the `appleRedirectHandlerPOST` API to check if the request was made by our Android app (You can skip checking the state if you only have a mobile app and no website). `sign_in_with_apple` requires us to parse the query params sent by apple and include them in the redirect URL in a specific way, and then we simply redirect to the deep link url. Refer to the README for `sign_in_with_apple` to read about the deep link setup required in Android. - -
      - -
      - -

      Step 2) Calling the signinup API to consume the authorisation token

      - - - -Once you have the authorisation code from the auth provider, you need to call the signinup API exposed by our backend SDK as shown below: - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup' \ ---header 'Content-Type: application/json; charset=utf-8' \ ---data-raw '{ - "thirdPartyId": "apple", - "clientType": "...", - "redirectURIInfo": { - "redirectURIOnProviderDashboard": "^{form_apiDomain}^{form_apiBasePath}/callback/apple", - "redirectURIQueryParams": { - "code": "...", - "user": { - "name":{ - "firstName":"...", - "lastName":"..." - }, - "email":"..." - } - } - } -}' -``` - -:::important -- On iOS, the client id set in the backend should be the same as the bundle identifier for your app. -- The `clientType` input is optional, and is required only if you have initialised more than one client in the provide on the backend (See the "Social / SSO login for both, web and mobile apps" section below). -- On iOS, `redirectURIOnProviderDashboard` doesn't matter and its value can be a universal link configured for your app. -- On Android, the `redirectURIOnProviderDashboard` should match the one configured on the Apple developer dashboard. -- The `user` object contains information provided by Apple. -::: - -The response body from the API call has a `status` property in it: -- `status: "OK"`: User sign in / up was successful. The response also contains more information about the user, for example their user ID, and if it was a new user or existing user. -- `status: "NO_EMAIL_GIVEN_BY_PROVIDER"`: This is returned if the social / SSO provider did not provider an email for the user. In this case, you want to ask the user to pick another method of sign in. Or, you can also override the backend functions to create a fake email for the user for this provider. -- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. -- `status: "SIGN_IN_UP_NOT_ALLOWED"`: This can happen during automatic account linking or during MFA. The `reason` prop that's in the response body contains a support code using which you can see why the sign in / up was not allowed. - -:::note -On success, the backend will send back session tokens as part of the response headers which will be automatically handled by our frontend SDK for you. -::: - - - -

      Sign in with Google Example

      - - - - - -

      Step 1) Fetching the authorisation token on the frontend

      - -This involves setting up the [@react-native-google-signin/google-signin](https://github.com/react-native-google-signin/google-signin) in your app. Checkout their `README` for steps on how to integrate their SDK into your application. The minimum scope required by SuperTokens is the one that gives the user's email. - -Once the library is set up, use `GoogleSignin.configure` and `GoogleSignin.signIn` to trigger the login flow and sign the user in with Google. Refer to [our example app](https://github.com/supertokens/supertokens-react-native/blob/master/examples/with-thirdparty/google.ts) to see the full code for this. - -```tsx -import { GoogleSignin } from "@react-native-google-signin/google-signin"; - -export const performGoogleSignIn = async (): Promise => { - GoogleSignin.configure({ - webClientId: "GOOGLE_WEB_CLIENT_ID", - iosClientId: "GOOGLE_IOS_CLIENT_ID", - }); - - try { - const user = await GoogleSignin.signIn({}); - const authCode = user.serverAuthCode; - - // Refer to step 2 - - return true; - } catch (e) { - console.log("Google sign in failed with error", e); - } - - return false; -}; -``` - -
      - - - -

      Step 1) Fetching the authorisation token on the frontend

      - -Follow the [official Google Sign In guide](https://developers.google.com/identity/sign-in/android/start-integrating) to set up their library and sign the user in with Google. Fetch the authorization code from the google sign in result. For a full example refer to the `signInWithGoogle` function in [our example app](https://github.com/supertokens/supertokens-android/blob/master/examples/with-thirdparty/app/src/main/java/com/supertokens/supertokensexample/LoginActivity.kt). - -```kotlin -import androidx.activity.result.ActivityResult -import androidx.activity.result.ActivityResultLauncher -import androidx.activity.result.contract.ActivityResultContracts -import androidx.appcompat.app.AppCompatActivity -import android.os.Bundle -import android.util.Log -import com.google.android.gms.auth.api.signin.GoogleSignIn -import com.google.android.gms.auth.api.signin.GoogleSignInOptions -import android.content.Intent - -class LoginActivity : AppCompatActivity() { - private lateinit var googleResultLauncher: ActivityResultLauncher - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - googleResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - onGoogleResultReceived(it) - } - } - - private fun signInWithGoogle() { - val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) - .requestServerAuthCode("GOOGLE_WEB_CLIENT_ID") - .requestEmail() - .build() - - val googleClient = GoogleSignIn.getClient(this, gso) - val signInIntent = googleClient.signInIntent - - googleResultLauncher.launch(signInIntent) - } - - private fun onGoogleResultReceived(it: ActivityResult) { - val task = GoogleSignIn.getSignedInAccountFromIntent(it.data) - val account = task.result - val authCode = account.serverAuthCode - - // Refer to step 2 - } -} -``` - -
      - - - -

      Step 1) Fetching the authorisation token on the frontend

      - -For iOS we use the `GoogleSignIn` library, follow the [official guide](https://developers.google.com/identity/sign-in/ios/start-integrating) to set up the library and sign the user in with Google. Use the result of google sign in to get the authorization code. For a full example refer to the `onGoogleCliked` function in [our example app](https://github.com/supertokens/supertokens-ios/blob/master/examples/with-thirdparty/with-thirdparty/LoginScreen/LoginScreenViewController.swift). - -```swift -import UIKit -import GoogleSignIn - -fileprivate class LoginScreenViewController: UIViewController { - @IBAction func onGoogleCliked() { - GIDSignIn.sharedInstance.signIn(withPresenting: self) { signInResult, error in - guard error == nil else { return } - - guard let authCode: String = signInResult?.serverAuthCode as? String else { - print("Google login did not return an authorisation code") - return - } - - // Refer to step 2 - } - } -} -``` - -
      - - - -

      Step 1) Fetching the authorisation token on the frontend

      - -For flutter we use the [google_sign_in](https://pub.dev/packages/google_sign_in) package, make sure to follow the prerequisites steps to get the package setup. After setup use the snippet below to trigger the google sign in flow. For a full example refer to the `loginWithGoogle` in [our example app](https://github.com/supertokens/supertokens-flutter/blob/master/examples/with-thirdparty/lib/screens/login.dart). - -```dart -import 'package:google_sign_in/google_sign_in.dart'; -import 'dart:io'; - -Future loginWithGoogle() async { - GoogleSignIn googleSignIn; - - if (Platform.isAndroid) { - googleSignIn = GoogleSignIn( - serverClientId: "GOOGLE_WEB_CLIENT_ID", - scopes: [ - 'email', - ], - ); - } else { - googleSignIn = GoogleSignIn( - clientId: "GOOGLE_IOS_CLIENT_ID", - serverClientId: "GOOGLE_WEB_CLIENT_ID", - scopes: [ - 'email', - ], - ); - } - - GoogleSignInAccount? account = await googleSignIn.signIn(); - - if (account == null) { - print("Google sign in was aborted"); - return; - } - - String? authCode = account.serverAuthCode; - - if (authCode == null) { - print("Google sign in did not return a server auth code"); - return; - } - - // Refer to step 2 - } -``` - -
      - -
      - -

      Step 2) Calling the signinup API to consume the authorisation token

      - - - -Once you have the authorisation code from the auth provider, you need to call the signinup API exposed by our backend SDK as shown below: - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup' \ ---header 'Content-Type: application/json; charset=utf-8' \ ---data-raw '{ - "thirdPartyId": "google", - "clientType": "...", - "redirectURIInfo": { - "redirectURIOnProviderDashboard": "", - "redirectURIQueryParams": { - "code": "...", - } - } -}' -``` - -:::important -When calling the API exposed by the SuperTokens backend SDK, we pass an empty string for `redirectURIOnProviderDashboard` because we use the native login flow using the authorization code which does not involve any redirection on the frontend. -::: - -The response body from the API call has a `status` property in it: -- `status: "OK"`: User sign in / up was successful. The response also contains more information about the user, for example their user ID, and if it was a new user or existing user. -- `status: "NO_EMAIL_GIVEN_BY_PROVIDER"`: This is returned if the social / SSO provider did not provider an email for the user. In this case, you want to ask the user to pick another method of sign in. Or, you can also override the backend functions to create a fake email for the user for this provider. -- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. -- `status: "SIGN_IN_UP_NOT_ALLOWED"`: This can happen during automatic account linking or during MFA. The `reason` prop that's in the response body contains a support code using which you can see why the sign in / up was not allowed. - -:::note -On success, the backend will send back session tokens as part of the response headers which will be automatically handled by our frontend SDK for you. -::: - - - -

      Flow 1b: Authorization code grant flow with PKCE

      - -This is similar to flow 1a, except that you do **not** need to provide a client secret during backend init. This flow only works for providers which support the [PKCE flow](https://oauth.net/2/pkce/). - - - - - -

      Step 1) Fetching the authorisation token on the frontend

      - -You can use the [react native auth library](https://github.com/FormidableLabs/react-native-app-auth) to also return the PKCE code verifier along with the authorization code. This can be done by setting the `usePKCE` boolean to `true` and also by setting the `skipCodeExchange` to `true` when configuring the react native auth library. - -
      - - - -

      Step 1) Fetching the authorisation token on the frontend

      - -You can use the [AppAuth-Android](https://github.com/openid/AppAuth-Android) library to use the PKCE flow by using the `setCodeVerifier` method when creating a `AuthorizationRequest`. - -
      - - - -

      Step 1) Fetching the authorisation token on the frontend

      - -You can use the [AppAuth-iOS](https://github.com/openid/AppAuth-iOS) library to use the PKCE flow. - -
      - - - -

      Step 1) Fetching the authorisation token on the frontend

      - -You can use [flutter_appauth](https://pub.dev/packages/flutter_appauth) to use the PKCE flow by providing a `codeVerifier` when you call the `appAuth.token` function. - -
      - -
      - -

      Step 2) Calling the signinup API to consume the authorisation token

      - - - -Once you have the authorisation code and PKCE verifier from the auth provider, you need to call the signinup API exposed by our backend SDK as shown below: - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "thirdPartyId": "THIRD_PARTY_ID", - "clientType": "...", - "redirectURIInfo": { - "redirectURIOnProviderDashboard": "REDIRECT_URI", - "redirectURIQueryParams": { - "code": "...", - }, - "pkceCodeVerifier": "..." - } -}' -``` - -:::important -- Replace `THIRD_PARTY_ID` with the provider id. The provider id must match the one you configure in the backend when intialising SuperTokens. -- `REDIRECT_URI` must exactly match the value you configure on the providers dashboard. -::: - -The response body from the API call has a `status` property in it: -- `status: "OK"`: User sign in / up was successful. The response also contains more information about the user, for example their user ID, and if it was a new user or existing user. -- `status: "NO_EMAIL_GIVEN_BY_PROVIDER"`: This is returned if the social / SSO provider did not provider an email for the user. In this case, you want to ask the user to pick another method of sign in. Or, you can also override the backend functions to create a fake email for the user for this provider. -- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. -- `status: "SIGN_IN_UP_NOT_ALLOWED"`: This can happen during automatic account linking or during MFA. The `reason` prop that's in the response body contains a support code using which you can see why the sign in / up was not allowed. - -:::note -On success, the backend will send back session tokens as part of the response headers which will be automatically handled by our frontend SDK for you. -::: - - - ----------------- - -

      Flow 2: Via OAuth / Access token

      - -

      Step 1) Fetching the OAuth / access tokens on the frontend

      - -1. Sign in with the social provider. The minimum required scope is the one that provides access to the user's email. You can use any library to sign in with the social provider. -2. Get the access token on the frontend if it is available. -3. Get the id token from the sign in result if it is available. - -:::important -You need to provide either the access token or the id token, or both in step 2, depending on what is available. -::: - -

      Step 2) Calling the signinup API to use the OAuth tokens

      - - - -Once you have the `access_token` or the `id_token` from the auth provider, you need to call the signinup API exposed by our backend SDK as shown below: - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup' \ ---header 'Content-Type: application/json; charset=utf-8' \ ---data-raw '{ - "thirdPartyId": "google", - "clientType": "...", - "oAuthTokens": { - "access_token": "...", - "id_token": "..." - }, -}' -``` - -:::important -- The `clientType` input is optional, and is required only if you have initialised more than one client in the provide on the backend (See the "Social / SSO login for both, web and mobile apps" section below). -- If you have the `id_token`, you can send that along with the `access_token`. -::: - -The response body from the API call has a `status` property in it: -- `status: "OK"`: User sign in / up was successful. The response also contains more information about the user, for example their user ID, and if it was a new user or existing user. -- `status: "NO_EMAIL_GIVEN_BY_PROVIDER"`: This is returned if the social / SSO provider did not provider an email for the user. In this case, you want to ask the user to pick another method of sign in. Or, you can also override the backend functions to create a fake email for the user for this provider. -- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. -- `status: "SIGN_IN_UP_NOT_ALLOWED"`: This can happen during automatic account linking or during MFA. The `reason` prop that's in the response body contains a support code using which you can see why the sign in / up was not allowed. - -:::note -On success, the backend will send back session tokens as part of the response headers which will be automatically handled by our frontend SDK for you. -::: - - - -
      - -
      - -## Social / SSO login for both, web and mobile apps - -If you have social / SSO login for your web and mobile app, then you might need to setup different client ID / secret for the same provider on the backend. For example, in case of Apple login, Apple gives you different client IDs for iOS login vs web & Android login (same client ID for web and Android). - -In order to get this to work, you would need to add additional clients to the Apple.init on the backend. Each client would need to be uniquely identified and this is done using the `clientType` string. For example, you can add one `clientType` for `web-and-android` and one for `ios`. - - - - -```tsx -import { ProviderInput } from "supertokens-node/recipe/thirdparty/types"; - -let providers: ProviderInput[] = [ - { - config: { - thirdPartyId: "apple", - clients: [{ - clientType: "web-and-android", - clientId: "...", - additionalConfig: { - "keyId": "...", - "privateKey": "...", - "teamId": "...", - } - }, { - clientType: "ios", - clientId: "...", - additionalConfig: { - "keyId": "...", - "privateKey": "...", - "teamId": "...", - } - }] - } - } -] -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" -) - -func main() { - _ = []tpmodels.ProviderInput{{ - Config: tpmodels.ProviderConfig{ - ThirdPartyId: "apple", - Clients: []tpmodels.ProviderClientConfig{ - { - ClientType: "web-and-android", - ClientID: "...", - AdditionalConfig: map[string]interface{}{ - "keyId": "...", - "privateKey": "...", - "teamId": "...", - }, - }, - { - ClientType: "ios", - ClientID: "...", - AdditionalConfig: map[string]interface{}{ - "keyId": "...", - "privateKey": "...", - "teamId": "...", - }, - }, - }, - }, - }} -} -``` - - - - -```python -from supertokens_python.recipe.thirdparty.provider import ProviderInput, ProviderConfig, ProviderClientConfig - -providers = [ - ProviderInput( - config=ProviderConfig( - third_party_id="apple", - clients=[ - ProviderClientConfig( - client_type="web-and-android", - client_id="...", - additional_config={ - "keyId": "...", - "privateKey": "...", - "teamId": "...", - }, - ), - ProviderClientConfig( - client_type="ios", - client_id="...", - additional_config={ - "keyId": "...", - "privateKey": "...", - "teamId": "...", - }, - ), - ], - ), - ), -] -``` - - - - -For the frontend, you would need to use the right `clientType` as shown below: - - - - - - - - - -We pass in the `clientType` during the init call. - -```tsx -import SuperTokens from 'supertokens-web-js'; - -SuperTokens.init({ - appInfo: { - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}", - appName: "...", - }, - // highlight-next-line - clientType: "web-and-android", - recipeList: [/*...*/], -}); -``` - -If you are using our pre built UI SDK (supertokens-auth-react) as well, you can provide the `clientType` config to it as follows: - -```tsx -import SuperTokens from 'supertokens-auth-react'; - -SuperTokens.init({ - appInfo: { - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - appName: "...", - }, - // highlight-next-line - clientType: "web-and-android", - recipeList: [/*...*/], -}); -``` - - - - - - - - -We pass in the `clientType` during the init call. - -```tsx -import supertokens from "supertokens-web-js-script"; -supertokens.init({ - appInfo: { - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}", - appName: "...", - }, - // highlight-next-line - clientType: "web-and-android", - recipeList: [/*...*/], -}); -``` - - - - - - - - - - -When making calls to the APIs from your mobile app, the request body also takes a `clientType` prop as seen in the above API calls. - - - - - - -## See also - -- [Post sign in / sign up action](../common-customizations/handling-signinup-success) -- [See all built in providers](../common-customizations/sign-in-and-up/built-in-providers) -- [Changing OAuth provider's scope](../common-customizations/sign-in-and-up/built-in-providers#setting-oauth-scopes) -- [Integrating with a custom OAuth provider](../common-customizations/sign-in-and-up/custom-providers) -- [Fetching profile info and using the provider's access token on the backend](../common-customizations/get-user-info) -- [Customising user ID format](../common-customizations/userid-format) diff --git a/v2/thirdparty/introduction.mdx b/v2/thirdparty/introduction.mdx index 88edfe0c1..1b61d2fbd 100644 --- a/v2/thirdparty/introduction.mdx +++ b/v2/thirdparty/introduction.mdx @@ -1,46 +1,12 @@ --- id: introduction -title: Introduction & Architecture +title: Introduction hide_title: true +hide_table_of_contents: true --- -# Social / Enterprise (OAuth 2.0, SAML) login +import Redirector from '/src/components/Redirector'; -## Features -- Sign-up / Sign-in with [OAuth 2.0 / OIDC / SAML providers](https://supertokens.com/features/single-sign-on) (Like Google, Facebook, Active Directory, etc)
      -- Secure session management
      -- Email verification
      -- Dynamic creation of multi tenant login experiences
      + - - - - -## Demo application - -- See our [live demo app](https://^{docsLinkRecipeName}.demo.supertokens.com/). -- We have a read-only user management dashboard (with fake data), and it can be accessed [on this link](https://dashboard.demo.supertokens.com/api/auth/dashboard). The credentials for logging in are: - ```text - email: demo@supertokens.com - password: abcd1234 - ``` -- Generate a starter app - ```bash - npx create-supertokens-app@latest --recipe=^{docsLinkRecipeName} - ``` -- The user management dashboard for the starter app can be accessed on `http://localhost:3001/auth/dashboard`. You will have to first create users (instrs are on the dashboard UI), before logging in. - -## Architecture - -import Architecture from "../community/reusableMD/intro-architecture.mdx" - - - -## Next steps - -import NextSteps from "../community/reusableMD/intro-next-steps.mdx" - - - - diff --git a/v2/thirdparty/pre-built-ui/enable-email-verification.mdx b/v2/thirdparty/pre-built-ui/enable-email-verification.mdx index 7b4646140..4f3ade3f4 100644 --- a/v2/thirdparty/pre-built-ui/enable-email-verification.mdx +++ b/v2/thirdparty/pre-built-ui/enable-email-verification.mdx @@ -4,814 +4,8 @@ title: Enable email verification hide_title: true --- -# Enable email verification +import Redirector from '/src/components/Redirector'; -:::important -For social / third party logins, the user's email is automatically marked as verified if the user had verified their email to the login provider. -::: - - - + -import CustomAdmonition from "/src/components/customAdmonition" -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"; - - -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 - - - - -```tsx -import SuperTokens from "supertokens-node"; -import EmailVerification from "supertokens-node/recipe/emailverification"; -import Session from "supertokens-node/recipe/session"; - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // highlight-start - EmailVerification.init({ - mode: "REQUIRED", // or "OPTIONAL" - }), - // highlight-end - Session.init(), - ], -}); -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/emailverification" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evmodels" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - supertokens.Init(supertokens.TypeInput{ - RecipeList: []supertokens.Recipe{ - // highlight-start - emailverification.Init(evmodels.TypeInput{ - Mode: evmodels.ModeRequired, // or evmodels.ModeOptional - }), - // highlight-end - session.Init(&sessmodels.TypeInput{}), - }, - }) -} -``` - - - - -```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import session -from supertokens_python.recipe import emailverification - -init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore - recipe_list=[ - # highlight-start - emailverification.init(mode='REQUIRED'), # or 'OPTIONAL' - # highlight-end - session.init() - ] -) -``` - - - - - - - - - - -import {Question}from "/src/components/question" - -## Step 2: Frontend setup - - - - - - - -```tsx -import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; -import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui"; -import EmailVerification from "supertokens-auth-react/recipe/emailverification"; -^{prebuiltuiimport} -import { EmailVerificationPreBuiltUI } from "supertokens-auth-react/recipe/emailverification/prebuiltui"; -import Session from "supertokens-auth-react/recipe/session"; -import reactRouterDOM, { Routes, BrowserRouter as Router, Route } from "react-router-dom"; - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // highlight-start - EmailVerification.init({ - mode: "REQUIRED", // or "OPTIONAL" - }), - // highlight-end - Session.init(), - ], -}); - -function App() { - return ( - -
      - -
      - - // highlight-start - {getSuperTokensRoutesForReactRouterDom(reactRouterDOM, [^{recipePreBuiltUINameCapitalLetters}, EmailVerificationPreBuiltUI])} - // highlight-end - // ... other routes - -
      -
      -
      -
      - ); -} -``` - -
      - - - -```tsx -import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; -import { canHandleRoute, getRoutingComponent } from "supertokens-auth-react/ui"; -import EmailVerification from "supertokens-auth-react/recipe/emailverification"; -^{prebuiltuiimport} -import { EmailVerificationPreBuiltUI } from "supertokens-auth-react/recipe/emailverification/prebuiltui"; -import Session from "supertokens-auth-react/recipe/session"; - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // highlight-start - EmailVerification.init({ - mode: "REQUIRED", // or "OPTIONAL" - }), - // highlight-end - Session.init(), - ], -}); - -function App() { - // highlight-start - if (canHandleRoute([^{recipePreBuiltUINameCapitalLetters}, EmailVerificationPreBuiltUI])) { - return getRoutingComponent([^{recipePreBuiltUINameCapitalLetters}, EmailVerificationPreBuiltUI]) - } - // highlight-end - return ( - {/*Your app*/} - ); -} -``` - - - -
      - -
      - - - -```tsx -// this goes in the auth route config of your frontend app (once the pre built UI script has been loaded) -import {init as supertokensUIInit} from "supertokens-auth-react-script"; -import supertokensUIEmailVerification from "supertokens-auth-react-script/recipe/emailverification"; -import supertokensUISession from "supertokens-auth-react-script/recipe/session"; - -supertokensUIInit({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // highlight-start - supertokensUIEmailVerification.init({ - mode: "REQUIRED", // or "OPTIONAL" - }), - // highlight-end - supertokensUISession.init(), - ], -}); -``` - - - -
      - - - - - - - -## Step 3: Checking if the user's email is verified in your APIs - -

      If using REQUIRED mode

      - -On the backend, when you initialize the email verification recipe in this mode, the `verifySession` middleware automatically checks if the user's email is verified based on the contents of the session's payload. If the email is not verified, the `verifySession` middleware will return a `403` status code to the client. - -

      If using OPTIONAL mode

      - -In this mode, you need to check if the email is verified yourself in the APIs in which you want this constraint. The verification status should already be in the session's payload. - - - - - - -```tsx -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import express from "express"; -import { SessionRequest } from "supertokens-node/framework/express"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let app = express(); - -app.post( - "/update-blog", - verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - }), - async (req: SessionRequest, res) => { - // All validator checks have passed and the user has a verified email address - } -); -``` - - - -```tsx -import Hapi from "@hapi/hapi"; -import { verifySession } from "supertokens-node/recipe/session/framework/hapi"; -import {SessionRequest} from "supertokens-node/framework/hapi"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let server = Hapi.server({ port: 8000 }); - -server.route({ - path: "/update-blog", - method: "post", - options: { - pre: [ - { - method: verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - }), - }, - ], - }, - handler: async (req: SessionRequest, res) => { - // All validator checks have passed and the user has a verified email address - } -}) -``` - - - -```tsx -import Fastify from "fastify"; -import { verifySession } from "supertokens-node/recipe/session/framework/fastify"; -import { SessionRequest } from "supertokens-node/framework/fastify"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let fastify = Fastify(); - -fastify.post("/update-blog", { - preHandler: verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - }), -}, async (req: SessionRequest, res) => { - // All validator checks have passed and the user has a verified email address -}); -``` - - - - -```tsx -import { verifySession } from "supertokens-node/recipe/session/framework/awsLambda"; -import { SessionEvent } from "supertokens-node/framework/awsLambda"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -async function updateBlog(awsEvent: SessionEvent) { - // All validator checks have passed and the user has a verified email address -}; - -exports.handler = verifySession(updateBlog, { - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] -}); -``` - - - - -```tsx -import KoaRouter from "koa-router"; -import { verifySession } from "supertokens-node/recipe/session/framework/koa"; -import {SessionContext} from "supertokens-node/framework/koa"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let router = new KoaRouter(); - -router.post("/update-blog", verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - }), async (ctx: SessionContext, next) => { - // All validator checks have passed and the user has a verified email address -}); -``` - - - - -```tsx -import { inject, intercept } from "@loopback/core"; -import { RestBindings, MiddlewareContext, post, response } from "@loopback/rest"; -import { verifySession } from "supertokens-node/recipe/session/framework/loopback"; -import Session from "supertokens-node/recipe/session"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -class SetRole { - constructor(@inject(RestBindings.Http.CONTEXT) private ctx: MiddlewareContext) { } - @post("/update-blog") - @intercept(verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - })) - @response(200) - async handler() { - // All validator checks have passed and the user has a verified email address - } -} -``` - - - - -```tsx -import { superTokensNextWrapper } from 'supertokens-node/nextjs' -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import { SessionRequest } from "supertokens-node/framework/express"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -// highlight-start -export default async function setRole(req: SessionRequest, res: any) { - await superTokensNextWrapper( - async (next) => { - await verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - })(req, res, next); - }, - req, - res - ) - // All validator checks have passed and the user has a verified email address -} -``` - - - - -```tsx -import SuperTokens from "supertokens-node"; -import { NextResponse, NextRequest } from "next/server"; -import { withSession } from "supertokens-node/nextjs"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; -// @ts-ignore -import { backendConfig } from "@/app/config/backend"; - -SuperTokens.init(backendConfig()); - -export async function POST(request: NextRequest) { - return withSession(request, async (err, session) => { - if (err) { - return NextResponse.json(err, { status: 500 }); - } - // All validator checks have passed and the user has a verified email address - return NextResponse.json({ message: "Your email is verified!" }); - }, - { - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - } - ); -} -``` - - - - -```tsx -import { Controller, Post, UseGuards, Request, Response, Session } from "@nestjs/common"; -import { SessionContainer, SessionClaimValidator } from "supertokens-node/recipe/session"; -// @ts-ignore -import { AuthGuard } from './auth/auth.guard'; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -@Controller() -export class ExampleController { - @Post('example') - @UseGuards(new AuthGuard({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators: SessionClaimValidator[]) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - })) - async postExample(@Session() session: SessionContainer): Promise { - // All validator checks have passed and the user has a verified email address - return true; - } -} -``` - - - - - - - - -```go -import ( - "net/http" - - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - _ = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - session.VerifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }, exampleAPI).ServeHTTP(rw, r) - }) -} - -func exampleAPI(w http.ResponseWriter, r *http.Request) { - // TODO: session is verified and all validators have passed.. -} -``` - - - - -```go -import ( - "net/http" - - "github.com/gin-gonic/gin" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - router := gin.New() - - // Wrap the API handler in session.VerifySession - router.POST("/likecomment", verifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }), exampleAPI) -} - -// This is a function that wraps the supertokens verification function -// to work the gin -func verifySession(options *sessmodels.VerifySessionOptions) gin.HandlerFunc { - return func(c *gin.Context) { - session.VerifySession(options, func(rw http.ResponseWriter, r *http.Request) { - c.Request = c.Request.WithContext(r.Context()) - c.Next() - })(c.Writer, c.Request) - // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly - c.Abort() - } -} - -func exampleAPI(c *gin.Context) { - // TODO: session is verified and all claim validators pass. -} -``` - - - - -```go -import ( - "net/http" - - "github.com/go-chi/chi" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - r := chi.NewRouter() - - // Wrap the API handler in session.VerifySession - r.Post("/likecomment", session.VerifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }, exampleAPI)) -} - -func exampleAPI(w http.ResponseWriter, r *http.Request) { - // TODO: session is verified and all claim validators pass. -} -``` - - - - -```go -import ( - "net/http" - - "github.com/gorilla/mux" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - router := mux.NewRouter() - - // Wrap the API handler in session.VerifySession - router.HandleFunc("/likecomment", session.VerifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }, exampleAPI)).Methods(http.MethodPost) -} - -func exampleAPI(w http.ResponseWriter, r *http.Request) { - // TODO: session is verified and all claim validators pass. -} -``` - - - - - - - - - -```python -from supertokens_python.recipe.session.framework.fastapi import verify_session -from supertokens_python.recipe.emailverification import EmailVerificationClaim -from supertokens_python.recipe.session import SessionContainer -from fastapi import Depends - -@app.post('/like_comment') # type: ignore -async def like_comment(session: SessionContainer = Depends( - verify_session( - # highlight-start - # We add the EmailVerificationClaim's is_verified validator - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \ - [EmailVerificationClaim.validators.is_verified()] - # highlight-end - ) -)): - # All validator checks have passed and the user has a verified email address - pass -``` - - - - -```python -from supertokens_python.recipe.session.framework.flask import verify_session -from supertokens_python.recipe.emailverification import EmailVerificationClaim - -@app.route('/update-jwt', methods=['POST']) # type: ignore -@verify_session( - # highlight-start - # We add the EmailVerificationClaim's is_verified validator - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \ - [EmailVerificationClaim.validators.is_verified()] - # highlight-end -) -def like_comment(): - # All validator checks have passed and the user has a verified email address - pass -``` - - - - -```python -from supertokens_python.recipe.session.framework.django.asyncio import verify_session -from django.http import HttpRequest -from supertokens_python.recipe.emailverification import EmailVerificationClaim - -@verify_session( - # highlight-start - # We add the EmailVerificationClaim's is_verified validator - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \ - [EmailVerificationClaim.validators.is_verified()] - # highlight-end -) -async def like_comment(request: HttpRequest): - # All validator checks have passed and the user has a verified email address - pass -``` - - - - - - - -We add the SDK's `EmailVerificationClaim` validator to the `verifySession` middleware call as shown above, and that will only allow access if the email is verified, else it will return `403` to the frontend. - - - - - - - -## Step 4: Protecting frontend routes - - - - -

      If using REQUIRED mode

      - -Wrapping your website routes using `` should enforce email verification. If the user's email is not verified, SuperTokens will automatically redirect the user to the email verification screen. - -

      If using OPTIONAL mode

      - -```tsx -import React from "react"; -import { SessionAuth, useSessionContext } from 'supertokens-auth-react/recipe/session'; -import { EmailVerificationClaim } from 'supertokens-auth-react/recipe/emailverification'; - -const VerifiedRoute = (props: React.PropsWithChildren) => { - return ( - - - {props.children} - - - ); -} - -function InvalidClaimHandler(props: React.PropsWithChildren) { - let sessionContext = useSessionContext(); - if (sessionContext.loading) { - return null; - } - - if (sessionContext.invalidClaims.some(i => i.id === EmailVerificationClaim.id)) { - // Alternatively you could redirect the user to the email verification screen to trigger the verification email - // Note: /auth/verify-email is the default email verification path - // window.location.assign("/auth/verify-email") - return
      You cannot access this page because your email address is not verified.
      - } - - // We show the protected route since all claims validators have - // passed implying that the user has verified their email. - return
      {props.children}
      ; -} -``` -Above we are creating a generic component called `VerifiedRoute` which enforces that its child components can only be rendered if the user has a verified email address. - -In the `VerifiedRoute` component, we use the `SessionAuth` wrapper to ensure that the session exists. The `SessionAuth` wrapper will create a context that contains a prop called `invalidClaims` which will contain a list of all claim validations that have failed. - -The email verification recipe on the frontend, adds the `EmailVerificationClaim` validator automatically, so if the user's email is not verified, the `invalidClaims` prop will contain information about that. Alternatively you could also redirect the user to the default email verification path to trigger the sending of the verification email. - -We check the result of the validation in the `InvalidClaimHandler` component which displays `"You cannot access this page because your email address is not verified."` if the `EmailVerificationClaim` validator failed. - -If all validations pass, we render the `props.children` component. - -
      - - - -```tsx -import Session from "supertokens-web-js/recipe/session"; -import { EmailVerificationClaim } from "supertokens-web-js/recipe/emailverification"; - -async function shouldLoadRoute(): Promise { - if (await Session.doesSessionExist()) { - // highlight-start - let validationErrors = await Session.validateClaims(); - - if (validationErrors.length === 0) { - // user has verified their email address - return true; - } else { - for (const err of validationErrors) { - if (err.id === EmailVerificationClaim.id) { - // email is not verified - } - } - } - // highlight-end - } - // a session does not exist, or email is not verified - return false -} -``` - -In your protected routes, you need to first check if a session exists, and then call the `Session.validateClaims` function as shown above. This function inspects the session's contents and runs claim validators on them. If a claim validator fails, it will be reflected in the `validationErrors` variable. The `EmailVerificationClaim` validator will be automatically checked by this function since you have initialized the email verification recipe. - - - -
      - - - - - - - -## See also - -- [Post email verification action](../common-customizations/email-verification/handling-email-verification-success) -- [Change email verification link's lifetime](../common-customizations/email-verification/changing-token-lifetime) -- [Customise email template or email delivery method](../email-delivery/about) -- [Manually changing email verification status for a user](../common-customizations/email-verification/changing-email-verification-status) -- [Generating email verification links manually](../common-customizations/email-verification/generate-link-manually) -- [Replacing, customising or embedding the frontend UI](../common-customizations/email-verification/embed-in-page) -- 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/thirdparty/pre-built-ui/handling-session-tokens.mdx b/v2/thirdparty/pre-built-ui/handling-session-tokens.mdx index 69b80421f..6ff934e75 100644 --- a/v2/thirdparty/pre-built-ui/handling-session-tokens.mdx +++ b/v2/thirdparty/pre-built-ui/handling-session-tokens.mdx @@ -4,165 +4,7 @@ title: Handling session tokens hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -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'; + -# Handling session tokens - - - - - -:::success -No action required. -::: - -Our frontend SDK handles everything for you. You only need to make sure that you have called `supertokens.init` before making any network requests. - -Our SDK adds interceptors to `fetch` and `XHR` (used by `axios`) to save and add session tokens from and to the request. - -By default, our web SDKs use cookies to provide credentials. - - - - - -## Getting the access token - -:::caution -Our SDK automatically handles adding the access token to request headers. You only need to add the access token to the request if you want to send the access token to a different API domain than what is configured on the frontend SDK init function call. -::: - -If you are using a header-based session or enabled `exposeAccessTokenToFrontendInCookieBasedAuth` (see below), you can read the access token on the frontend using the `getAccessToken` method: - - - - - - - - -```tsx -import Session from "supertokens-auth-react/recipe/session"; - -async function getToken(): Promise { - // highlight-next-line - const accessToken = await Session.getAccessToken(); - console.log(accessToken); -} -``` - - - - - -```tsx -import Session from "supertokens-web-js/recipe/session"; - -async function getToken(): Promise { - // highlight-next-line - const accessToken = await Session.getAccessToken(); - console.log(accessToken); -} -``` - - - - - - - -### If using cookie-based sessions - -:::caution -This will expose the access token to the frontend, meaning it could be vulnerable to XSS attacks. -::: - -:::important -If you are using header-based sessions, you can skip this step -::: - -By enabling this setting, you'll expose the access token to your frontend code even if you use cookies for authentication. - - - - - - - - -```tsx -import SuperTokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; - -SuperTokens.init({ - supertokens: { - connectionURI: "..." - }, - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "..." - }, - recipeList: [ - Session.init({ - //highlight-start - exposeAccessTokenToFrontendInCookieBasedAuth: true, - //highlight-end - }) - ] -}); -``` - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - supertokens.Init(supertokens.TypeInput{ - RecipeList: []supertokens.Recipe{ - session.Init(&sessmodels.TypeInput{ - //highlight-start - ExposeAccessTokenToFrontendInCookieBasedAuth: true, - //highlight-end - }), - }, - }) -} -``` - - - -```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import session - -init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore - recipe_list=[ - session.init( - # highlight-next-line - expose_access_token_to_frontend_in_cookie_based_auth=True - ) - ] -) -``` - - - - - diff --git a/v2/thirdparty/pre-built-ui/multitenant-login.mdx b/v2/thirdparty/pre-built-ui/multitenant-login.mdx index 1b42a09aa..bf2091f6c 100644 --- a/v2/thirdparty/pre-built-ui/multitenant-login.mdx +++ b/v2/thirdparty/pre-built-ui/multitenant-login.mdx @@ -4,418 +4,8 @@ title: "Multitenant and Enterprise SSO login" hide_title: true --- -import AppInfoForm from "/src/components/appInfoForm" -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import PythonSyncAsyncSubTabs from "/src/components/tabs/PythonSyncAsyncSubTabs"; -import CoreVersionSubTabs from "/src/components/tabs/CoreVersionSubTabs"; -import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs"; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import MultiTenancyPaidBanner from '../../community/reusableMD/multitenancy/MultiTenancyPaidBanner.mdx' -import CoreInjector from "/src/components/coreInjector" -import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" +import Redirector from '/src/components/Redirector'; - - - - + -# Multitenant and Enterprise SSO login - -Multitenant login is a feature that lets you customize the login experience for each of your customers. For example, a customer `customer1` hosted on `customer1.yourdomain.com` can have login with `Active Directory` and `Google`, and another customer `customer2` hosted on `customer2.yourdomain.com` can have login with `Okta` and `Facebook`. - -This is also the page that you should see if you want to implement sign in with: -- Okta (`thirdPartyId: "okta"`) -- SAML (`thirdPartyId: "boxy-saml"`) -- Active Directory (`thirdPartyId: "active-directory"`) -- Google Workspaces (`thirdPartyId: "google-workspaces"`) -- GitLab (`thirdPartyId: "gitlab"`) -- Bitbucket (`thirdPartyId: "bitbucket"`) -- Or any other workforce IdP - - - - - - - -## Step 1: Create and configure a new tenant in SuperTokens core - -Each tenant can be configured with a unique `tenantId`, and the list of third party connections (Active Directory, Google etc..) that should be allowed for them. - -You can create a new tenant using our backend SDKs or via a `cURL` command to the core. - - - - - - - - - - - -```tsx -import Multiteancy from "supertokens-node/recipe/multitenancy"; - -async function createTenant() { - let resp = await Multiteancy.createOrUpdateTenant("customer1", { - firstFactors: ["thirdparty"] - }); - - if (resp.createdNew) { - // new tenant was created - } else { - // existing tenant's config was modified. - } -} -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/multitenancy" - "github.com/supertokens/supertokens-golang/recipe/multitenancy/multitenancymodels" -) - -func main() { - tenantId := "customer1" - thirdPartyEnabled := true - - // highlight-start - resp, err := multitenancy.CreateOrUpdateTenant(tenantId, multitenancymodels.TenantConfig{ - ThirdPartyEnabled: &thirdPartyEnabled, - }) - // highlight-end - - if err != nil { - // handle error - } - if resp.OK.CreatedNew { - // new tenant was created - } else { - // existing tenant's config was modified. - } -} -``` - - - - - - - -```python -from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate - -async def some_func(): - result = await create_or_update_tenant( - "public", - TenantConfigCreateOrUpdate( - first_factors=["thirdparty"], - ), - ) - - if result.status != "OK": - print("handle error") - elif result.created_new: - print("new tenant was created") - else: - print("existing tenant's config was modified.") -``` - - - - - -```python -from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate - -def some_func(): - result = create_or_update_tenant( - "public", - TenantConfigCreateOrUpdate( - first_factors=["thirdparty"], - ), - ) - - if result.status != "OK": - print("handle error") - elif result.created_new: - print("new tenant was created") - else: - print("existing tenant's config was modified.") -``` - - - - - - - - - - - - - - -```bash -curl --location --request PUT '^{coreInjector_uri_without_quotes}/recipe/multitenancy/tenant/v2' \ ---header 'api-key: ^{coreInjector_api_key_without_quotes}' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "tenantId": "customer1", - "firstFactors": ["thirdparty"] -}' -``` - - - - - -```bash -curl --location --request PUT '^{coreInjector_uri_without_quotes}/recipe/multitenancy/tenant' \ ---header 'api-key: ^{coreInjector_api_key_without_quotes}' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "tenantId": "customer1", - "thirdPartyEnabled": true -}' -``` - - - - - - - - - - - -Create Tenant - - - - - - - - - - - -Once a tenant is created, add their thirdparty providers as shown below. - - - - - - - - - - - -```tsx -import Multiteancy from "supertokens-node/recipe/multitenancy"; - -async function createTenant() { - let resp = await Multiteancy.createOrUpdateThirdPartyConfig("customer1", { - thirdPartyId: "active-directory", - name: "Active Directory", - clients: [{ - clientId: "...", - clientSecret: "...", - }], - oidcDiscoveryEndpoint: "https://login.microsoftonline.com//v2.0/.well-known/openid-configuration", - }); - - if (resp.createdNew) { - // Active Directory added to customer1 - } else { - // Existing active directory config overwritten for customer1 - } -} -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/multitenancy" - "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" -) - -func main() { - tenantId := "customer1" - - // highlight-start - resp, err := multitenancy.CreateOrUpdateThirdPartyConfig(tenantId, tpmodels.ProviderConfig{ - ThirdPartyId: "active-directory", - Name: "Active Directory", - Clients: []tpmodels.ProviderClientConfig{ - { - ClientID: "...", - ClientSecret: "...", - }, - }, - OIDCDiscoveryEndpoint: "https://login.microsoftonline.com//v2.0/.well-known/openid-configuration", - }, nil, nil) - // highlight-end - - if err != nil { - // handle error - } - if resp.OK.CreatedNew { - // Active Directory added to customer1 - } else { - // Existing active directory config overwritten for customer1 - } -} -``` - - - - - - - -```python -from supertokens_python.recipe.multitenancy.asyncio import create_or_update_third_party_config -from supertokens_python.recipe.thirdparty.provider import ProviderConfig, ProviderClientConfig - -async def update_tenant(): - result = await create_or_update_third_party_config( - "customer1", - config=ProviderConfig( - third_party_id="active-directory", - name="Active Directory", - clients=[ - ProviderClientConfig( - client_id="...", - client_secret="...", - ) - ], - oidc_discovery_endpoint="https://login.microsoftonline.com//v2.0/.well-known/openid-configuration", - ), - ) - - if result.status != "OK": - print("Error adding active directory to tenant") - elif result.created_new: - print("Active directory was added to the tenant") - else: - print("Existing tenant's active directory config was modified") -``` - - - - - -```python -from supertokens_python.recipe.multitenancy.syncio import create_or_update_third_party_config -from supertokens_python.recipe.thirdparty.provider import ProviderConfig, ProviderClientConfig - -result = create_or_update_third_party_config( - "customer1", - config=ProviderConfig( - third_party_id="active-directory", - name="Active Directory", - clients=[ - ProviderClientConfig( - client_id="...", - client_secret="...", - ) - ] - ), - ) - -if result.status != "OK": - print("Error creating or updating tenant") -elif result.created_new: - print("New tenant was created") -else: - print("Existing tenant's config was modified") -``` - - - - - - - - - - -```bash -curl --location --request PUT '^{coreInjector_uri_without_quotes}//recipe/multitenancy/config/thirdparty' \ ---header 'api-key: ^{coreInjector_api_key_without_quotes}' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "config": { - "thirdPartyId": "active-directory", - "name": "Active Directory", - "clients": [ - { - "clientId": "...", - "clientSecret": "..." - } - ], - "oidcDiscoveryEndpoint": "https://login.microsoftonline.com//v2.0/.well-known/openid-configuration" - } -}' -``` - - - - - - - -Create Third Party Provider - - - - - - - - - - - -:::important -The above shows how to add an Active Directory config for the `customer1` tenant. You can see the config structure for all the in built providers [on this page](../common-customizations/sign-in-and-up/provider-config). -::: - - - - - - - -## Step 2: Build your multi tenant a UX flow - -The most common multi tenant flows are: -- [Tenants use a common domain to login](../common-customizations/multi-tenancy/common-domain-login): All tenants login using the same page (for example, `example.com/auth`) and are optionally redirected to their sub domain post login. At the start of the login flow, the customer will have to input their tenantId / workspace URL / identifier - as defined by you, and the login methods shown would be based on their tenantId. -- [Tenants use their sub domain to login](../common-customizations/multi-tenancy/sub-domain-login): Here, each tenant has a sub domain assigned to them (for example `customer1.example.com`, `customer2.example.com`, ...), and they would visit their sub domain to login and access their app. Each sub domain's login experience may be different (as defined by you or the tenant). - -SuperTokens is flexible enough to allow other forms of UX as well, but since the above two flow are most common, we provide dedicated docs for them (see the links above). - -## See also - -- Multi tenant [single domain](../common-customizations/multi-tenancy/common-domain-login) or [sub domain](../common-customizations/multi-tenancy/sub-domain-login) login. -- [See all built in multitenant providers](../common-customizations/sign-in-and-up/provider-config). -- [See how to add custom multitenant provider](../common-customizations/multi-tenancy/custom-provider). -- [SAML login](../common-customizations/saml/what-is-saml). -- [Tenant data isolation](../common-customizations/multi-tenancy/new-tenant-config#providing-additional-configuration-per-tenant). - - diff --git a/v2/thirdparty/pre-built-ui/securing-routes.mdx b/v2/thirdparty/pre-built-ui/securing-routes.mdx index a45ea028c..adf124a95 100644 --- a/v2/thirdparty/pre-built-ui/securing-routes.mdx +++ b/v2/thirdparty/pre-built-ui/securing-routes.mdx @@ -4,530 +4,8 @@ title: "Securing your API and frontend routes" hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -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 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 + -## Protecting APIs - - - - - - - -### Requiring an active session - -For your APIs that require a user to be logged in, use the `verifySession` middleware - - - - - - -```tsx -import express from "express"; -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import { SessionRequest } from "supertokens-node/framework/express"; - -let app = express(); - -// highlight-start -app.post("/like-comment", verifySession(), (req: SessionRequest, res) => { - let userId = req.session!.getUserId(); - // highlight-end - //.... -}); -``` - - - - -```tsx -import Hapi from "@hapi/hapi"; -import { verifySession } from "supertokens-node/recipe/session/framework/hapi"; -import { SessionRequest } from "supertokens-node/framework/hapi"; - -let server = Hapi.server({ port: 8000 }); - -server.route({ - path: "/like-comment", - method: "post", - //highlight-start - options: { - pre: [ - { - method: verifySession() - }, - ], - }, - handler: async (req: SessionRequest, res) => { - let userId = req.session!.getUserId(); - //highlight-end - //... - } -}) -``` - - - -```tsx -import Fastify from "fastify"; -import { verifySession } from "supertokens-node/recipe/session/framework/fastify"; -import { SessionRequest } from "supertokens-node/framework/fastify"; - -let fastify = Fastify(); - -//highlight-start -fastify.post("/like-comment", { - preHandler: verifySession(), -}, (req: SessionRequest, res) => { - let userId = req.session!.getUserId(); - //highlight-end - //.... -}); -``` - - - - -```tsx -import { verifySession } from "supertokens-node/recipe/session/framework/awsLambda"; -import { SessionEventV2 } from "supertokens-node/framework/awsLambda"; - -async function likeComment(awsEvent: SessionEventV2) { - let userId = awsEvent.session!.getUserId(); - //.... -}; - -//highlight-next-line -exports.handler = verifySession(likeComment); -``` - - - - -```tsx -import KoaRouter from "koa-router"; -import { verifySession } from "supertokens-node/recipe/session/framework/koa"; -import { SessionContext } from "supertokens-node/framework/koa"; - -let router = new KoaRouter(); - -//highlight-start -router.post("/like-comment", verifySession(), (ctx: SessionContext, next) => { - let userId = ctx.session!.getUserId(); - //highlight-end - //.... -}); -``` - - - - -```tsx -import { inject, intercept } from "@loopback/core"; -import { RestBindings, MiddlewareContext, post, response } from "@loopback/rest"; -import { verifySession } from "supertokens-node/recipe/session/framework/loopback"; -import { SessionContext } from "supertokens-node/framework/loopback"; - -class LikeComment { - //highlight-start - constructor(@inject(RestBindings.Http.CONTEXT) private ctx: MiddlewareContext) { } - @post("/like-comment") - @intercept(verifySession()) - @response(200) - handler() { - let userId = (this.ctx as SessionContext).session!.getUserId(); - //highlight-end - //.... - } -} -``` - - - - -```tsx -import { superTokensNextWrapper } from 'supertokens-node/nextjs' -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import { SessionRequest } from "supertokens-node/framework/express"; - -// highlight-start -export default async function likeComment(req: SessionRequest, res: any) { - await superTokensNextWrapper( - async (next) => { - await verifySession()(req, res, next); - }, - req, - res - ) - - let userId = req.session!.getUserId(); - // highlight-end - //.... -} -``` - - - - -```tsx -import { NextResponse, NextRequest } from "next/server"; -import SuperTokens from "supertokens-node"; -import { withSession } from "supertokens-node/nextjs"; -// @ts-ignore -import { backendConfig } from "@/app/config/backend"; - -SuperTokens.init(backendConfig()); - -export function POST(request: NextRequest) { - return withSession(request, async (err, session) => { - if (err) { - return NextResponse.json(err, { status: 500 }); - } - const userId = session!.getUserId(); - //.... - return NextResponse.json({}); - }); -} -``` - - - - -```tsx -import { Controller, Post, UseGuards, Session } from "@nestjs/common"; -import { SessionContainer } from "supertokens-node/recipe/session"; -// @ts-ignore -import { AuthGuard } from './auth/auth.guard'; - -@Controller() -export class ExampleController { - @Post('example') - @UseGuards(new AuthGuard()) // For more information about this guard please read our NestJS guide. - async postExample(@Session() session: SessionContainer): Promise { - //highlight-start - let userId = session.getUserId(); - - //highlight-end - //.... - return true; - } -} -``` - - - - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/supertokens/supertokens-golang/recipe/session" -) - -func main() { - _ = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - // Wrap the API handler in session.VerifySession - session.VerifySession(nil, likeCommentAPI).ServeHTTP(rw, r) - }) -} - -func likeCommentAPI(w http.ResponseWriter, r *http.Request) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(r.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/gin-gonic/gin" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" -) - -func main() { - router := gin.New() - - // Wrap the API handler in session.VerifySession - router.POST("/likecomment", verifySession(nil), likeCommentAPI) -} - -// This is a function that wraps the supertokens verification function -// to work the gin -func verifySession(options *sessmodels.VerifySessionOptions) gin.HandlerFunc { - return func(c *gin.Context) { - session.VerifySession(options, func(rw http.ResponseWriter, r *http.Request) { - c.Request = c.Request.WithContext(r.Context()) - c.Next() - })(c.Writer, c.Request) - // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly - c.Abort() - } -} - -func likeCommentAPI(c *gin.Context) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(c.Request.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/go-chi/chi" - "github.com/supertokens/supertokens-golang/recipe/session" -) - -func main() { - r := chi.NewRouter() - - // Wrap the API handler in session.VerifySession - r.Post("/likecomment", session.VerifySession(nil, likeCommentAPI)) -} - -func likeCommentAPI(w http.ResponseWriter, r *http.Request) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(r.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/gorilla/mux" - "github.com/supertokens/supertokens-golang/recipe/session" -) - -func main() { - router := mux.NewRouter() - - // Wrap the API handler in session.VerifySession - router.HandleFunc("/likecomment", session.VerifySession(nil, likeCommentAPI)).Methods(http.MethodPost) -} - -func likeCommentAPI(w http.ResponseWriter, r *http.Request) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(r.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - - - - - - -```python -from supertokens_python.recipe.session.framework.fastapi import verify_session -from supertokens_python.recipe.session import SessionContainer -from fastapi import Depends - -# highlight-start -@app.post('/like_comment') # type: ignore -async def like_comment(session: SessionContainer = Depends(verify_session())): - user_id = session.get_user_id() - # highlight-end - - print(user_id) -``` - - - - -```python -from supertokens_python.recipe.session.framework.flask import verify_session -from supertokens_python.recipe.session import SessionContainer -from flask import g - -# highlight-start -@app.route('/update-jwt', methods=['POST']) # type: ignore -@verify_session() -def like_comment(): - session: SessionContainer = g.supertokens # type: ignore - - user_id = session.get_user_id() - # highlight-end - - print(user_id) -``` - - - - -```python -from supertokens_python.recipe.session.framework.django.asyncio import verify_session -from django.http import HttpRequest -from supertokens_python.recipe.session import SessionContainer - -# highlight-start -@verify_session() -async def like_comment(request: HttpRequest): - session: SessionContainer = request.supertokens # type: ignore - - user_id = session.get_user_id() - # highlight-end - - print(user_id) -``` - - - - - - - -The `verifySession` function returns a `401` to the frontend if a session doesn't exist, or if the access token has expired, in which case, our frontend SDK automatically refreshes the session. - -In case of successful session verification, you get access to a `session` object using which you can get the user's ID, or manipulate the session information. - - - - - - - -### Microservice authentication - -For authentication between microservices on your backend, checkout the [microservice auth guide](/docs/microservice_auth/client-credentials). - - - -## Protecting website routes - - - - - -:::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. - -::: - - - - -You can wrap your components with the `` react component. This will ensure that your component renders only if the user is logged in. If they are not logged in, the user will be redirected to the login page. - -```tsx -import React from "react"; -import { - BrowserRouter, - Routes, - Route, -} from "react-router-dom"; -// highlight-next-line -import { SessionAuth } from "supertokens-auth-react/recipe/session"; -// @ts-ignore -import MyDashboardComponent from "./dashboard"; - -class App extends React.Component { - render() { - return ( - - - - {/*Components that require to be protected by authentication*/} - - - // highlight-end - } /> - - - ); - } -} -``` - - - - - -You can use the `doesSessionExist` function to check if a session exists in all your routes. - -```tsx -import Session from 'supertokens-web-js/recipe/session'; - -async function doesSessionExist() { - if (await Session.doesSessionExist()) { - // user is logged in - } else { - // user has not logged in yet - } -} -``` - - - - - - - - - - - -## See also - -- Optional sessions [for APIs](../common-customizations/sessions/session-verification-in-api/verify-session#optional-session-verification) and [the frontend](../common-customizations/sessions/checking-session-front-end) -- [Verifying session without using a middleware](../common-customizations/sessions/session-verification-in-api/get-session) -- Session claim validation [for APIs](../common-customizations/sessions/claims/claim-validators) and [the frontend](../common-customizations/sessions/claims/claim-validators) -- [Changing session lifetime](../common-customizations/sessions/change-session-timeout) -- [Sharing session across sub domains](../common-customizations/sessions/share-sessions-across-sub-domains) - - diff --git a/v2/thirdparty/pre-built-ui/setup/backend.mdx b/v2/thirdparty/pre-built-ui/setup/backend.mdx index 1fcaf312b..577fc5a32 100644 --- a/v2/thirdparty/pre-built-ui/setup/backend.mdx +++ b/v2/thirdparty/pre-built-ui/setup/backend.mdx @@ -4,1134 +4,8 @@ title: "Step 2: Backend" hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import TabItem from '@theme/TabItem'; -import NodeJSFrameworkSubTabsServerless from "/src/components/tabs/NodeJSFrameworkSubTabsServerless" -import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs" -import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" -import AppInfoForm from "/src/components/appInfoForm" -import CoreInjector from "/src/components/coreInjector" -import {Question, Answer}from "/src/components/question" -import BackendSDKCasing from "/src/components/BackendSDKCasing" -# Backend Integration + -## Supported frameworks - -import TechStackSupport from "../../../community/reusableMD/supported-tech-stacks-backend.mdx" - - - -## 1) Install - - - - -```bash -npm i -s supertokens-node -``` - - - - -```bash -go get github.com/supertokens/supertokens-golang -``` - - - - -```bash -pip install supertokens-python -``` - - - - -## 2) Initialise SuperTokens - - - - - -Add the code below to your server's init file. - - - - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import ThirdParty from "supertokens-node/recipe/thirdparty"; - -supertokens.init({ - framework: "express", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - // @ts-ignore - ThirdParty.init({/*TODO: See next step*/}), - Session.init() // initializes session features - ] -}); -``` - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import ThirdParty from "supertokens-node/recipe/thirdparty"; - -supertokens.init({ - framework: "hapi", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - // @ts-ignore - ThirdParty.init({/*TODO: See next step*/}), - Session.init() // initializes session features - ] -}); -``` - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import ThirdParty from "supertokens-node/recipe/thirdparty"; - -supertokens.init({ - framework: "fastify", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - // @ts-ignore - ThirdParty.init({/*TODO: See next step*/}), - Session.init() // initializes session features - ] -}); -``` - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import ThirdParty from "supertokens-node/recipe/thirdparty"; - -supertokens.init({ - framework: "koa", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - // @ts-ignore - ThirdParty.init({/*TODO: See next step*/}), - Session.init() // initializes session features - ] -}); -``` - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import ThirdParty from "supertokens-node/recipe/thirdparty"; - -supertokens.init({ - framework: "loopback", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - // @ts-ignore - ThirdParty.init({/*TODO: See next step*/}), - Session.init() // initializes session features - ] -}); -``` - - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/thirdparty" - "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - apiBasePath := "^{form_apiBasePath}" - websiteBasePath := "^{form_websiteBasePath}" - err := supertokens.Init(supertokens.TypeInput{ - Supertokens: &supertokens.ConnectionInfo{ - ^{coreInjector_connection_uri_comment} - ConnectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}APIKey: ^{coreInjector_api_key}, - }, - AppInfo: supertokens.AppInfo{ - AppName: "^{form_appName}", - APIDomain: "^{form_apiDomain}", - WebsiteDomain: "^{form_websiteDomain}", - APIBasePath: &apiBasePath, - WebsiteBasePath: &websiteBasePath, - }, - RecipeList: []supertokens.Recipe{ - thirdparty.Init(&tpmodels.TypeInput{/*TODO: See next step*/}), - session.Init(nil), // initializes session features - }, - }) - - if err != nil { - panic(err.Error()) - } -} -``` - - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig -from supertokens_python.recipe import thirdparty, session - -init( - app_info=InputAppInfo( - app_name="^{form_appName}", - api_domain="^{form_apiDomain}", - website_domain="^{form_websiteDomain}", - api_base_path="^{form_apiBasePath}", - website_base_path="^{form_websiteBasePath}" - ), - supertokens_config=SupertokensConfig( - ^{coreInjector_connection_uri_comment_with_hash} - connection_uri=^{coreInjector_uri} - ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} - ), - framework='fastapi', - recipe_list=[ - session.init(), # initializes session features - thirdparty.init( - # TODO: See next step - ) # type: ignore - ], - mode='asgi' # use wsgi if you are running using gunicorn -) -``` - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig -from supertokens_python.recipe import thirdparty, session - -init( - app_info=InputAppInfo( - app_name="^{form_appName}", - api_domain="^{form_apiDomain}", - website_domain="^{form_websiteDomain}", - api_base_path="^{form_apiBasePath}", - website_base_path="^{form_websiteBasePath}" - ), - supertokens_config=SupertokensConfig( - ^{coreInjector_connection_uri_comment_with_hash} - connection_uri=^{coreInjector_uri} - ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} - ), - framework='flask', - recipe_list=[ - session.init(), # initializes session features - thirdparty.init( - # TODO: See next step - ) # type: ignore - ] -) -``` - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig -from supertokens_python.recipe import thirdparty, session - -init( - app_info=InputAppInfo( - app_name="^{form_appName}", - api_domain="^{form_apiDomain}", - website_domain="^{form_websiteDomain}", - api_base_path="^{form_apiBasePath}", - website_base_path="^{form_websiteBasePath}" - ), - supertokens_config=SupertokensConfig( - ^{coreInjector_connection_uri_comment_with_hash} - connection_uri=^{coreInjector_uri} - ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} - ), - framework='django', - recipe_list=[ - session.init(), # initializes session features - thirdparty.init( - # TODO: See next step - ) # type: ignore - ], - mode='asgi' # use wsgi if you are running django server in sync mode -) -``` - - - - - - - - - - - -## 3) Initialise Social login providers - - - -Populate the `providers` array with the third party auth providers you want. - - - - -```tsx -import SuperTokens from "supertokens-node"; -import ThirdParty from "supertokens-node/recipe/thirdparty"; - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - ThirdParty.init({ - //highlight-start - signInAndUpFeature: { - // We have provided you with development keys which you can use for testing. - // IMPORTANT: Please replace them with your own OAuth keys for production use. - providers: [{ - config: { - thirdPartyId: "google", - clients: [{ - clientId: "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", - clientSecret: "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" - }] - } - }, { - config: { - thirdPartyId: "github", - clients: [{ - clientId: "467101b197249757c71f", - clientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd" - }] - } - }, { - config: { - thirdPartyId: "apple", - clients: [{ - clientId: "4398792-io.supertokens.example.service", - additionalConfig: { - keyId: "7M48Y4RYDL", - privateKey: - "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----", - teamId: "YWQCXGJRJL", - } - }] - } - }], - } - //highlight-end - }), - // ... - ] -}); -``` - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/thirdparty" - "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" -) - -func main() { - // Inside supertokens.Init - thirdparty.Init(&tpmodels.TypeInput{ - // highlight-start - SignInAndUpFeature: tpmodels.TypeInputSignInAndUp{ - Providers: []tpmodels.ProviderInput{ - // We have provided you with development keys which you can use for testing. - // IMPORTANT: Please replace them with your own OAuth keys for production use. - { - Config: tpmodels.ProviderConfig{ - ThirdPartyId: "google", - Clients: []tpmodels.ProviderClientConfig{ - { - ClientID: "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", - ClientSecret: "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", - }, - }, - }, - }, - { - Config: tpmodels.ProviderConfig{ - ThirdPartyId: "github", - Clients: []tpmodels.ProviderClientConfig{ - { - ClientID: "467101b197249757c71f", - ClientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd", - }, - }, - }, - }, - { - Config: tpmodels.ProviderConfig{ - ThirdPartyId: "apple", - Clients: []tpmodels.ProviderClientConfig{ - { - ClientID: "4398792-io.supertokens.example.service", - AdditionalConfig: map[string]interface{}{ - "keyId": "7M48Y4RYDL", - "privateKey": "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----", - "teamId": "YWQCXGJRJL", - }, - }, - }, - }, - }, - }, - }, - // highlight-end - }) -} -``` - - - -```python -from supertokens_python.recipe.thirdparty.provider import ProviderInput, ProviderConfig, ProviderClientConfig -from supertokens_python.recipe import thirdparty - -# Inside init -thirdparty.init( - # highlight-start - sign_in_and_up_feature=thirdparty.SignInAndUpFeature(providers=[ - # We have provided you with development keys which you can use for testing. - # IMPORTANT: Please replace them with your own OAuth keys for production use. - ProviderInput( - config=ProviderConfig( - third_party_id="google", - clients=[ - ProviderClientConfig( - client_id="1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", - client_secret="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", - ), - ], - ), - ), - ProviderInput( - config=ProviderConfig( - third_party_id="github", - clients=[ - ProviderClientConfig( - client_id="467101b197249757c71f", - client_secret="e97051221f4b6426e8fe8d51486396703012f5bd", - ) - ], - ), - ), - ProviderInput( - config=ProviderConfig( - third_party_id="apple", - clients=[ - ProviderClientConfig( - client_id="io.supertokens.example.service", - additional_config={ - "keyId": "7M48Y4RYDL", - "privateKey": "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----", - "teamId": "YWQCXGJRJL" - }, - ), - ], - ), - ), - ]) - # highlight-end -) -``` - - - - -**When you want to generate your own keys**, please refer to the corresponding documentation to get your client ids and client secrets for each of the below providers: - -
      -Google - -- Generate your client ID and secret by following the [docs here](https://support.google.com/cloud/answer/6158849?hl=en) -- Set the authorisation callback URL to `^{form_websiteDomain}^{form_websiteBasePathForCallbacks}/callback/google` - -
      - -
      -Github - -- Generate your client ID and secret by following the [docs here](https://docs.github.com/en/developers/apps/creating-an-oauth-app) -- Set the authorisation callback URL to `^{form_websiteDomain}^{form_websiteBasePathForCallbacks}/callback/github` - -
      - -
      -Apple - -- Generate your client ID and secret by following [this article](https://medium.com/identity-beyond-borders/how-to-configure-sign-in-with-apple-77c61e336003) -- Set the authorisation callback URL to `^{form_apiDomain}^{form_apiBasePathForCallbacks}/callback/apple`. Note that Apple doesn't allow `localhost` in the URL. So if you are in dev mode, you can use the dev keys we have provided above. - -
      - -:::important -You can find the list of built in providers [here](../../common-customizations/sign-in-and-up/built-in-providers). To add a provider that is not listed, you can follow our guide on [setting up custom providers](../../common-customizations/sign-in-and-up/custom-providers). -::: - -
      - -## 4) Add the SuperTokens APIs & CORS setup - - - - - - - - -:::important -- Add the `middleware` BEFORE all your routes. -- Add the `cors` middleware BEFORE the SuperTokens middleware as shown below. -::: - -```tsx -import express from "express"; -import cors from "cors"; -import supertokens from "supertokens-node"; -import { middleware } from "supertokens-node/framework/express"; - -let app = express(); - -app.use(cors({ - // highlight-start - origin: "^{form_websiteDomain}", - allowedHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], - credentials: true, - // highlight-end -})); - -// IMPORTANT: CORS should be before the below line. -// highlight-next-line -app.use(middleware()); - -// ...your API routes -``` -This `middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): - -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. - - - - -Register the `plugin`. - -```tsx -import Hapi from "@hapi/hapi"; -import supertokens from "supertokens-node"; -import { plugin } from "supertokens-node/framework/hapi"; - -let server = Hapi.server({ - port: 8000, - routes: { - // highlight-start - cors: { - origin: ["^{form_websiteDomain}"], - additionalHeaders: [...supertokens.getAllCORSHeaders()], - credentials: true, - } - // highlight-end - } -}); - -(async () => { - // highlight-next-line - await server.register(plugin); - - await server.start(); -})(); - -// ...your API routes -``` -This `plugin` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)) as well take care of all the errors thrown by the Supertokens library: - -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. - - - - -Register the `plugin`. Also register [`@fastify/formbody`](https://github.com/fastify/fastify-formbody) plugin. - -```tsx -import cors from "@fastify/cors"; -import supertokens from "supertokens-node"; -import { plugin } from "supertokens-node/framework/fastify"; -import formDataPlugin from "@fastify/formbody"; - -import fastifyImport from "fastify"; - -let fastify = fastifyImport(); - -// ...other middlewares -// highlight-start -fastify.register(cors, { - origin: "^{form_websiteDomain}", - allowedHeaders: ['Content-Type', ...supertokens.getAllCORSHeaders()], - credentials: true, -}); -// highlight-end - -(async () => { - // highlight-next-line - await fastify.register(formDataPlugin); - // highlight-next-line - await fastify.register(plugin); - - await fastify.listen(8000); -})(); - -// ...your API routes -``` -This `plugin` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): - -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. - - - - -:::important -Add the `middleware` BEFORE all your routes. -::: - -```tsx -import Koa from "koa"; -import cors from '@koa/cors'; -import supertokens from "supertokens-node"; -import { middleware } from "supertokens-node/framework/koa"; - -let app = new Koa(); - -// ...other middlewares -app.use(cors({ - // highlight-start - origin: "^{form_websiteDomain}", - allowHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], - credentials: true, - // highlight-end -})); - -// highlight-next-line -app.use(middleware()); - -// ...your API routes -``` -This `middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): - -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. - - - - -:::important -Add the `middleware` BEFORE all your routes. -::: - -```tsx -import { RestApplication } from "@loopback/rest"; -import supertokens from "supertokens-node"; -import { middleware } from "supertokens-node/framework/loopback"; - -let app = new RestApplication({ - rest: { - cors: { - // highlight-start - origin: "^{form_websiteDomain}", - allowedHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], - credentials: true - // highlight-end - } - } -}); - -// highlight-next-line -app.middleware(middleware); - -// ...your API routes -``` -This `middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): - -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. - - - - - - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "net/http" - "strings" - - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - //highlight-start - http.ListenAndServe("SERVER ADDRESS", corsMiddleware( - supertokens.Middleware(http.HandlerFunc(func(rw http.ResponseWriter, - //highlight-end - r *http.Request) { - // TODO: Handle your APIs.. - - })))) -} - -func corsMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(response http.ResponseWriter, r *http.Request) { - response.Header().Set("Access-Control-Allow-Origin", "^{form_websiteDomain}") - response.Header().Set("Access-Control-Allow-Credentials", "true") - if r.Method == "OPTIONS" { - // we add content-type + other headers used by SuperTokens - response.Header().Set("Access-Control-Allow-Headers", - strings.Join(append([]string{"Content-Type"}, - //highlight-start - supertokens.GetAllCORSHeaders()...), ",")) - //highlight-end - response.Header().Set("Access-Control-Allow-Methods", "*") - response.Write([]byte("")) - } else { - next.ServeHTTP(response, r) - } - }) -} -``` - - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "net/http" - - "github.com/gin-contrib/cors" - "github.com/gin-gonic/gin" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - router := gin.New() - - // CORS - router.Use(cors.New(cors.Config{ - AllowOrigins: []string{"^{form_websiteDomain}"}, - AllowMethods: []string{"GET", "POST", "DELETE", "PUT", "OPTIONS"}, - AllowHeaders: append([]string{"content-type"}, - // highlight-next-line - supertokens.GetAllCORSHeaders()...), - AllowCredentials: true, - })) - - // Adding the SuperTokens middleware - // highlight-start - router.Use(func(c *gin.Context) { - supertokens.Middleware(http.HandlerFunc( - func(rw http.ResponseWriter, r *http.Request) { - c.Next() - })).ServeHTTP(c.Writer, c.Request) - // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly - c.Abort() - }) - // highlight-end - - // Add APIs and start server -} -``` - - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "github.com/go-chi/chi" - "github.com/go-chi/cors" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - r := chi.NewRouter() - - // CORS - r.Use(cors.Handler(cors.Options{ - AllowedOrigins: []string{"^{form_websiteDomain}"}, - AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, - AllowedHeaders: append([]string{"Content-Type"}, - //highlight-next-line - supertokens.GetAllCORSHeaders()...), - AllowCredentials: true, - })) - - // SuperTokens Middleware - //highlight-next-line - r.Use(supertokens.Middleware) - - // Add APIs and start server -} -``` - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "net/http" - - "github.com/gorilla/handlers" - "github.com/gorilla/mux" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - // Add APIs - - router := mux.NewRouter() - - // Adding handlers.CORS(options)(supertokens.Middleware(router))) - //highlight-start - http.ListenAndServe("SERVER ADDRESS", handlers.CORS( - handlers.AllowedHeaders(append([]string{"Content-Type"}, - supertokens.GetAllCORSHeaders()...)), - handlers.AllowedMethods([]string{"GET", "POST", "PUT", "HEAD", "OPTIONS"}), - handlers.AllowedOrigins([]string{"^{form_websiteDomain}"}), - handlers.AllowCredentials(), - )(supertokens.Middleware(router))) - //highlight-end -} -``` - - - -This `Middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): - -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. - - - - - - - -Use the `Middleware` (**BEFORE all your routes**) and the `get_all_cors_headers()` functions as shown below. - -```python -from supertokens_python import get_all_cors_headers -from fastapi import FastAPI -from starlette.middleware.cors import CORSMiddleware -from supertokens_python.framework.fastapi import get_middleware - -app = FastAPI() -# highlight-next-line -app.add_middleware(get_middleware()) - -# TODO: Add APIs - -app.add_middleware( - CORSMiddleware, - allow_origins=[ - "^{form_websiteDomain}" - ], - allow_credentials=True, - allow_methods=["GET", "PUT", "POST", "DELETE", "OPTIONS", "PATCH"], - # highlight-next-line - allow_headers=["Content-Type"] + get_all_cors_headers(), -) - -# TODO: start server -``` - - - - -- Use the `Middleware` (**BEFORE all your routes and after calling init function**) and the `get_all_cors_headers()` functions as shown below. -- Add a route to catch all paths and return a 404. This is needed because if we don't add this, then OPTIONS request for the APIs exposed by the `Middleware` will return a `404`. - -```python -from supertokens_python import get_all_cors_headers -from flask import Flask, abort -from flask_cors import CORS # type: ignore -from supertokens_python.framework.flask import Middleware - -app = Flask(__name__) -# highlight-next-line -Middleware(app) - -# TODO: Add APIs - -CORS( - app=app, - origins=[ - "^{form_websiteDomain}" - ], - supports_credentials=True, - # highlight-next-line - allow_headers=["Content-Type"] + get_all_cors_headers(), -) - -# This is required since if this is not there, then OPTIONS requests for -# the APIs exposed by the supertokens' Middleware will return a 404 -# highlight-start -@app.route('/', defaults={'u_path': ''}) # type: ignore -@app.route('/') # type: ignore -def catch_all(u_path: str): - abort(404) -# highlight-end - -# TODO: start server -``` - - - - -Use the `Middleware` and the `get_all_cors_headers()` functions as shown below in your `settings.py`. - -```python -from supertokens_python import get_all_cors_headers -from typing import List -from corsheaders.defaults import default_headers - -CORS_ORIGIN_WHITELIST = [ - "^{form_websiteDomain}" -] - -CORS_ALLOW_CREDENTIALS = True - -CORS_ALLOWED_ORIGINS = [ - "^{form_websiteDomain}" -] - -CORS_ALLOW_HEADERS: List[str] = list(default_headers) + [ - "Content-Type" - # highlight-next-line -] + get_all_cors_headers() - -INSTALLED_APPS = [ - 'corsheaders', - 'supertokens_python' -] - -MIDDLEWARE = [ - 'corsheaders.middleware.CorsMiddleware', - ..., - # highlight-next-line - 'supertokens_python.framework.django.django_middleware.middleware', -] -``` - - - - -This `Middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): - -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. - - - - - - -## 5) Add the SuperTokens error handler -Add the `errorHandler` **AFTER all your routes**, but **BEFORE your error handler** - - - - - - -```tsx -import express, { Request, Response, NextFunction } from "express"; -import { errorHandler } from "supertokens-node/framework/express"; - -let app = express(); -// ...your API routes - -// highlight-start -// Add this AFTER all your routes -app.use(errorHandler()) -// highlight-end - -// your own error handler -app.use((err: unknown, req: Request, res: Response, next: NextFunction) => { /* ... */ }); -``` - - - -No additional `errorHandler` is required. - - - - -Add the `errorHandler` **Before all your routes and plugin registration** - -```tsx -import Fastify from "fastify"; -import { errorHandler } from "supertokens-node/framework/fastify"; - -let fastify = Fastify(); - -// highlight-next-line -fastify.setErrorHandler(errorHandler()); - -// ...your API routes -``` - - - -No additional `errorHandler` is required. - - - - -No additional `errorHandler` is required. - - - - - - -:::info -You can skip this step -::: - - - - -:::info -You can skip this step -::: - - - - -## 6) Setup the SuperTokens core - -You need to now setup an instance of the SuperTokens core for your app (that your backend should connect to). You have two options: -- [Managed service](./core/saas-setup) -- Self hosted with your own database ([With Docker](./core/with-docker) or [Without Docker](./core/without-docker)) \ No newline at end of file diff --git a/v2/thirdparty/pre-built-ui/setup/frontend.mdx b/v2/thirdparty/pre-built-ui/setup/frontend.mdx index abd34c3ab..bac6ce88b 100644 --- a/v2/thirdparty/pre-built-ui/setup/frontend.mdx +++ b/v2/thirdparty/pre-built-ui/setup/frontend.mdx @@ -3,697 +3,9 @@ id: frontend title: "Step 1: Frontend" hide_title: true --- - - - -import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" -import TabItem from '@theme/TabItem'; -import {Question, Answer}from "/src/components/question" -import AppInfoForm from "/src/components/appInfoForm" -import RRDVersionSubTabs from "/src/components/tabs/RRDVersionSubTabs" -import NpmVersionOrYarnSubTabs from "/src/components/tabs/NpmVersionOrYarnSubTabs" -import PreBuiltUIInjector from "/src/components/prebuiltuiInjector" +import Redirector from '/src/components/Redirector'; -# Frontend Integration -## Supported frameworks + -import TechStackSupport from "../../../community/reusableMD/supported-tech-stacks-frontend.mdx" - - - -# Automatic setup using CLI - -Run the following command in your terminal. -```bash -npx create-supertokens-app@latest --recipe=^{docsLinkRecipeName} -``` - -Once this is done, you can skip Step (1) and (2) in this section (see left nav bar) and move directly to setting up the SuperTokens core (Step 3). - -Or, you can manually integrate SuperTokens by following the steps below. - -# Manual setup steps below - -## 1) Install - - - - - - - - -```bash -npm i -s supertokens-auth-react -``` - - - - -```bash -npm i -s supertokens-auth-react supertokens-web-js -``` - - - - -```bash -yarn add supertokens-auth-react supertokens-web-js -``` - - - - - - - - - -Start by installing the SuperTokens Web SDK: - -```bash -npm i -s supertokens-web-js -``` - - - - - -Start by installing the SuperTokens Web SDK: - -```bash -npm i -s supertokens-web-js -``` - - - - - -Start by installing the SuperTokens Web SDK: - -```bash -yarn add supertokens-web-js -``` - - - - - - - - - - - -Start by installing the SuperTokens web SDK: - -```bash -npm i -s supertokens-web-js -``` - - - - - -Start by installing the SuperTokens web SDK: - -```bash -npm i -s supertokens-web-js -``` - - - - - -Start by installing the SuperTokens web SDK: - -```bash -yarn add supertokens-web-js -``` - - - - - - - - -:::important -SuperTokens does not support pre-built UI for mobile frameworks. Please refer to the [setup guide for custom UI](../../custom-ui/init/frontend) to integrate SuperTokens in your app. -::: - - - - - - - -## 2) Call the `init` function - - - - - - -```tsx -import React from 'react'; - -// highlight-start -import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; -import ThirdParty, {Github, Google, Facebook, Apple} from "supertokens-auth-react/recipe/thirdparty"; -import Session from "supertokens-auth-react/recipe/session"; - -SuperTokens.init({ - appInfo: { - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - ThirdParty.init({ - signInAndUpFeature: { - providers: [ - Github.init(), - Google.init(), - Facebook.init(), - Apple.init(), - ] - } - }), - Session.init() - ] -}); -// highlight-end - - -/* Your App */ -class App extends React.Component { - render() { - return ( - // highlight-next-line - - {/*Your app components*/} - // highlight-next-line - - ); - } -} -``` - - - - - - - - - -```tsx -import SuperTokens from 'supertokens-react-native'; - -SuperTokens.init({ - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}" -}); -``` - - - - - - - - - - - -Before we initialize the `supertokens-web-js` SDK let's see how we will use it in our Angular app - -**Architecture** - -- The `supertokens-web-js` SDK is responsible for session management and providing helper functions to check if a session exists, or validate the access token claims on the frontend (for example, to check for user roles before showing some UI). We will initialise this SDK on the root of your Angular app, so that all pages in your app can use it. -- We will create a `^{form_websiteBasePath}*` route in the Angular app which will render our pre built UI which will also need to be initialised, but only on that route. - -**Creating the `^{form_websiteBasePath}` route** - -- Use the Angular CLI to generate a new route - - ```bash - ng generate module auth --route auth --module app.module - ``` - -- Add the following code to your `auth` angular component - - ```tsx title="/app/auth/auth.component.ts" - import {init as supertokensUIInit} from "supertokens-auth-react-script"; - import supertokensUIThirdParty from "supertokens-auth-react-script/recipe/thirdparty"; - import supertokensUISession from "supertokens-auth-react-script/recipe/session"; - 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: [ - supertokensUIThirdParty.init({ - signInAndUpFeature: { - providers: [ - supertokensUIThirdParty.Github.init(), - supertokensUIThirdParty.Google.init(), - supertokensUIThirdParty.Facebook.init(), - supertokensUIThirdParty.Apple.init(), - ] - } - }), - supertokensUISession.init(), - ], - }); - } - this.renderer.appendChild(this.document.body, script); - } - } - ``` - - In the `loadScript` function, we provide the SuperTokens config for the UI. We add the session and social login recipes along with Github, Google, Facebook and Apple login buttons. - -- Initialize the `supertokens-web-js` SDK in your angular app's root component. This will provide session management across your entire application. - - ```tsx title="/app/app.component.ts " - import SuperTokens from "supertokens-web-js"; - import Session from "supertokens-web-js/recipe/session"; - - SuperTokens.init({ - appInfo: { - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}", - }, - recipeList: [ - Session.init(), - ], - }); - ``` - -
      - -
      - -
      - - - - - - - -Before we initialize the `supertokens-web-js` SDK let's see how we will use it in our Vue app - -**Architecture** - -- The `supertokens-web-js` SDK is responsible for session management and providing helper functions to check if a session exists, or validate the access token claims on the frontend (for example, to check for user roles before showing some UI). We will initialise this SDK on the root of your Vue app, so that all pages in your app can use it. -- We will create a `^{form_websiteBasePath}*` route in the Vue app which will render our pre built UI which will also need to be initialised, but only on that route. - -**Creating the `^{form_websiteBasePath}` route** - -- Create a new file `AuthView.vue`, this Vue component will be used to render the auth component: - ```tsx - import {init as supertokensUIInit} from "supertokens-auth-react-script"; - import supertokensUIThirdParty from "supertokens-auth-react-script/recipe/thirdparty"; - import supertokensUISession from "supertokens-auth-react-script/recipe/session"; - - - - ``` - - In the `loadScript` function, we provide the SuperTokens config for the UI. We add the session and social login recipes along with Github, Google, Facebook and Apple login buttons. - -- Initialize the `supertokens-web-js` SDK in your Vue app's `main.ts` file. This will provide session management across your entire application. - - ```tsx title="/main.ts " - // @ts-ignore - import { createApp } from "vue"; - import SuperTokens from "supertokens-web-js"; - import Session from "supertokens-web-js/recipe/session"; - // @ts-ignore - import App from "./App.vue"; - // @ts-ignore - import router from "./router"; - - SuperTokens.init({ - appInfo: { - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}", - }, - recipeList: [ - Session.init(), - ], - }); - - const app = createApp(App); - - app.use(router); - - app.mount("#app"); - - ``` - - - - - - - - - -:::important -SuperTokens does not support pre-built UI for mobile frameworks. Please refer to the [setup guide for custom UI](../../custom-ui/init/frontend) to integrate SuperTokens in your app. -::: - - - -
      - - - - - - -## 3) Setup Routing to show the login UI - - - - - - - - - - - -Call the `getSuperTokensRoutesForReactRouterDom` method from within any `react-router-dom` `Routes` component. - -```tsx -import React from 'react'; -import { - BrowserRouter, - Routes, - Route, - Link -} from "react-router-dom"; - -// highlight-next-line -import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; -import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui"; -^{prebuiltuiimport} -import * as reactRouterDom from "react-router-dom"; - -class App extends React.Component { - render() { - return ( - - - - {/*This renders the login UI on the ^{form_websiteBasePath} route*/} - // highlight-next-line - {getSuperTokensRoutesForReactRouterDom(reactRouterDom, [^{recipePreBuiltUINameCapitalLetters}])} - {/*Your app routes*/} - - - - ); - } -} -``` - - - - - -Call the `getSuperTokensRoutesForReactRouterDom` method from within any `react-router-dom` `Switch` component. - -```tsx -import React from 'react'; -import { - BrowserRouter, - Switch, - Route, - Link -} from "react-router-dom5"; - -// highlight-next-line -import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; -import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui"; -^{prebuiltuiimport} -import * as reactRouterDom from "react-router-dom"; - - -class App extends React.Component { - render() { - return ( - - - - {/*This renders the login UI on the ^{form_websiteBasePath} route*/} - // highlight-next-line - {getSuperTokensRoutesForReactRouterDom(reactRouterDom, [^{recipePreBuiltUINameCapitalLetters}])} - {/*Your app routes*/} - - - - ); - } -} -``` - - - - - - - - -Add the highlighted code snippet to your root level `render` function. - -```tsx -import React from 'react'; -^{prebuiltuiimport} -import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; -import { canHandleRoute, getRoutingComponent } from "supertokens-auth-react/ui"; - -class App extends React.Component { - render() { - // highlight-start - if (canHandleRoute([^{recipePreBuiltUINameCapitalLetters}])) { - // This renders the login UI on the ^{form_websiteBasePath} route - return getRoutingComponent([^{recipePreBuiltUINameCapitalLetters}]) - } - // highlight-end - - return ( - {/*Your app*/} - ); - } - -} -``` - - - - - - - - - - -Update your angular router so that all auth related requests load the `auth` component - -```tsx title="/app/app-routing.module.ts" -import { NgModule } from "@angular/core"; -import { RouterModule, Routes } from "@angular/router"; - -const routes: Routes = [ - // highlight-start - { - path: "^{form_websiteBasePath_withoutForwardSlash}", - // @ts-ignore - loadChildren: () => import("./auth/auth.module").then((m) => m.AuthModule), - }, - - // @ts-ignore - { path: "**", loadChildren: () => import("./home/home.module").then((m) => m.HomeModule) }, - // highlight-end -]; - -@NgModule({ - imports: [RouterModule.forRoot(routes)], - exports: [RouterModule], -}) -export class AppRoutingModule {} -``` - - - - - - - - -Update your Vue router so that all auth related requests load the `AuthView` component - -```tsx title="/router/index.ts" -// @ts-ignore -import { createRouter, createWebHistory } from "vue-router"; -// @ts-ignore -import HomeView from "../views/HomeView.vue"; -// @ts-ignore -import AuthView from "../views/AuthView.vue"; - -const router = createRouter({ - // @ts-ignore - history: createWebHistory(import.meta.env.BASE_URL), - routes: [ - { - path: "/", - name: "home", - component: HomeView, - }, - { - path: "^{form_websiteBasePath}/:pathMatch(.*)*", - name: "auth", - component: AuthView, - }, - ], -}); - -export default router; -``` - - - - - - - -:::important -SuperTokens does not support pre-built UI for mobile frameworks. Please refer to the [setup guide for custom UI](../../custom-ui/init/frontend) to integrate SuperTokens in your app. -::: - - - - - - - -## 4) View the login UI - - -^{form_addVisitWebsiteBasePathText} - - - -At this stage, you've successfully integrated your website with SuperTokens. The next section will guide you through setting up your backend. - diff --git a/v2/thirdparty/quickstart/backend-setup.mdx b/v2/thirdparty/quickstart/backend-setup.mdx new file mode 100644 index 000000000..80e2501b8 --- /dev/null +++ b/v2/thirdparty/quickstart/backend-setup.mdx @@ -0,0 +1,1489 @@ +--- +id: backend-setup +title: Backend Setup +hide_title: true +show_ui_switcher: false +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +import PreBuiltUIInjector from "/src/components/prebuiltuiInjector" +import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; +import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" +import {Question, Answer}from "/src/components/question" +import RRDVersionSubTabs from "/src/components/tabs/RRDVersionSubTabs" +import AppInfoForm from "/src/components/appInfoForm" +import NodeJSFrameworkSubTabsServerless from "/src/components/tabs/NodeJSFrameworkSubTabsServerless" +import CoreInjector from "/src/components/coreInjector" +import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs" +import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" +import BackendSDKCasing from "/src/components/BackendSDKCasing" + + + +# Backend Setup + +Let's got through the changes required so that your backend can expose the **SuperTokens** authentication features. + +## 1. Install the SDK + + + + + +Run the following command in your terminal to install the package. + + + + +```bash +npm i -s supertokens-node +``` + + + + +```bash +go get github.com/supertokens/supertokens-golang +``` + + + + +```bash +pip install supertokens-python +``` + + + + + + +## 2. Initialize the SDK + +You will have to intialize the **Backend SDK** alongside the code that starts your server. +The init call will include [configuration details](../appinfo) for your app, how the backend will connect to the **SuperTokens Core**, as well as the **Recipes** that will be used in your setup. + + + + + +Add the code below to your server's init file. + + + + + + + +```tsx +import supertokens from "supertokens-node"; +import Session from "supertokens-node/recipe/session"; +import ThirdParty from "supertokens-node/recipe/thirdparty"; + +supertokens.init({ + framework: "express", + supertokens: { + ^{coreInjector_connection_uri_comment} + connectionURI: ^{coreInjector_uri} + ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, + }, + appInfo: { + // learn more about this on https://supertokens.com/docs/session/appinfo + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + websiteDomain: "^{form_websiteDomain}", + apiBasePath: "^{form_apiBasePath}", + websiteBasePath: "^{form_websiteBasePath}" + }, + recipeList: [ + // @ts-ignore + ThirdParty.init({/*TODO: See next step*/}), + Session.init() // initializes session features + ] +}); +``` + + + + +```tsx +import supertokens from "supertokens-node"; +import Session from "supertokens-node/recipe/session"; +import ThirdParty from "supertokens-node/recipe/thirdparty"; + +supertokens.init({ + framework: "hapi", + supertokens: { + ^{coreInjector_connection_uri_comment} + connectionURI: ^{coreInjector_uri} + ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, + }, + appInfo: { + // learn more about this on https://supertokens.com/docs/session/appinfo + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + websiteDomain: "^{form_websiteDomain}", + apiBasePath: "^{form_apiBasePath}", + websiteBasePath: "^{form_websiteBasePath}" + }, + recipeList: [ + // @ts-ignore + ThirdParty.init({/*TODO: See next step*/}), + Session.init() // initializes session features + ] +}); +``` + + + + +```tsx +import supertokens from "supertokens-node"; +import Session from "supertokens-node/recipe/session"; +import ThirdParty from "supertokens-node/recipe/thirdparty"; + +supertokens.init({ + framework: "fastify", + supertokens: { + ^{coreInjector_connection_uri_comment} + connectionURI: ^{coreInjector_uri} + ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, + }, + appInfo: { + // learn more about this on https://supertokens.com/docs/session/appinfo + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + websiteDomain: "^{form_websiteDomain}", + apiBasePath: "^{form_apiBasePath}", + websiteBasePath: "^{form_websiteBasePath}" + }, + recipeList: [ + // @ts-ignore + ThirdParty.init({/*TODO: See next step*/}), + Session.init() // initializes session features + ] +}); +``` + + + + +```tsx +import supertokens from "supertokens-node"; +import Session from "supertokens-node/recipe/session"; +import ThirdParty from "supertokens-node/recipe/thirdparty"; + +supertokens.init({ + framework: "koa", + supertokens: { + ^{coreInjector_connection_uri_comment} + connectionURI: ^{coreInjector_uri} + ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, + }, + appInfo: { + // learn more about this on https://supertokens.com/docs/session/appinfo + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + websiteDomain: "^{form_websiteDomain}", + apiBasePath: "^{form_apiBasePath}", + websiteBasePath: "^{form_websiteBasePath}" + }, + recipeList: [ + // @ts-ignore + ThirdParty.init({/*TODO: See next step*/}), + Session.init() // initializes session features + ] +}); +``` + + + + +```tsx +import supertokens from "supertokens-node"; +import Session from "supertokens-node/recipe/session"; +import ThirdParty from "supertokens-node/recipe/thirdparty"; + +supertokens.init({ + framework: "loopback", + supertokens: { + ^{coreInjector_connection_uri_comment} + connectionURI: ^{coreInjector_uri} + ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, + }, + appInfo: { + // learn more about this on https://supertokens.com/docs/session/appinfo + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + websiteDomain: "^{form_websiteDomain}", + apiBasePath: "^{form_apiBasePath}", + websiteBasePath: "^{form_websiteBasePath}" + }, + recipeList: [ + // @ts-ignore + ThirdParty.init({/*TODO: See next step*/}), + Session.init() // initializes session features + ] +}); +``` + + + + + +```go +import ( + "github.com/supertokens/supertokens-golang/recipe/session" + "github.com/supertokens/supertokens-golang/recipe/thirdparty" + "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" + "github.com/supertokens/supertokens-golang/supertokens" +) + +func main() { + apiBasePath := "^{form_apiBasePath}" + websiteBasePath := "^{form_websiteBasePath}" + err := supertokens.Init(supertokens.TypeInput{ + Supertokens: &supertokens.ConnectionInfo{ + ^{coreInjector_connection_uri_comment} + ConnectionURI: ^{coreInjector_uri} + ^{coreInjector_api_key_commented}APIKey: ^{coreInjector_api_key}, + }, + AppInfo: supertokens.AppInfo{ + AppName: "^{form_appName}", + APIDomain: "^{form_apiDomain}", + WebsiteDomain: "^{form_websiteDomain}", + APIBasePath: &apiBasePath, + WebsiteBasePath: &websiteBasePath, + }, + RecipeList: []supertokens.Recipe{ + thirdparty.Init(&tpmodels.TypeInput{/*TODO: See next step*/}), + session.Init(nil), // initializes session features + }, + }) + + if err != nil { + panic(err.Error()) + } +} +``` + + + + + +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import thirdparty, session + +init( + app_info=InputAppInfo( + app_name="^{form_appName}", + api_domain="^{form_apiDomain}", + website_domain="^{form_websiteDomain}", + api_base_path="^{form_apiBasePath}", + website_base_path="^{form_websiteBasePath}" + ), + supertokens_config=SupertokensConfig( + ^{coreInjector_connection_uri_comment_with_hash} + connection_uri=^{coreInjector_uri} + ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} + ), + framework='fastapi', + recipe_list=[ + session.init(), # initializes session features + thirdparty.init( + # TODO: See next step + ) # type: ignore + ], + mode='asgi' # use wsgi if you are running using gunicorn +) +``` + + + + +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import thirdparty, session + +init( + app_info=InputAppInfo( + app_name="^{form_appName}", + api_domain="^{form_apiDomain}", + website_domain="^{form_websiteDomain}", + api_base_path="^{form_apiBasePath}", + website_base_path="^{form_websiteBasePath}" + ), + supertokens_config=SupertokensConfig( + ^{coreInjector_connection_uri_comment_with_hash} + connection_uri=^{coreInjector_uri} + ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} + ), + framework='flask', + recipe_list=[ + session.init(), # initializes session features + thirdparty.init( + # TODO: See next step + ) # type: ignore + ] +) +``` + + + + +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import thirdparty, session + +init( + app_info=InputAppInfo( + app_name="^{form_appName}", + api_domain="^{form_apiDomain}", + website_domain="^{form_websiteDomain}", + api_base_path="^{form_apiBasePath}", + website_base_path="^{form_websiteBasePath}" + ), + supertokens_config=SupertokensConfig( + ^{coreInjector_connection_uri_comment_with_hash} + connection_uri=^{coreInjector_uri} + ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} + ), + framework='django', + recipe_list=[ + session.init(), # initializes session features + thirdparty.init( + # TODO: See next step + ) # type: ignore + ], + mode='asgi' # use wsgi if you are running django server in sync mode +) +``` + + + + + + + + + + +## 3. Add the Authentication Providers + + + + +Populate the `providers` array with the third party authentication providers that you want. + + + + +```tsx +import SuperTokens from "supertokens-node"; +import ThirdParty from "supertokens-node/recipe/thirdparty"; + +SuperTokens.init({ + appInfo: { + apiDomain: "...", + appName: "...", + websiteDomain: "...", + }, + recipeList: [ + ThirdParty.init({ + //highlight-start + signInAndUpFeature: { + // We have provided you with development keys which you can use for testing. + // IMPORTANT: Please replace them with your own OAuth keys for production use. + providers: [{ + config: { + thirdPartyId: "google", + clients: [{ + clientId: "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", + clientSecret: "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" + }] + } + }, { + config: { + thirdPartyId: "github", + clients: [{ + clientId: "467101b197249757c71f", + clientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd" + }] + } + }, { + config: { + thirdPartyId: "apple", + clients: [{ + clientId: "4398792-io.supertokens.example.service", + additionalConfig: { + keyId: "7M48Y4RYDL", + privateKey: + "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----", + teamId: "YWQCXGJRJL", + } + }] + } + }], + } + //highlight-end + }), + // ... + ] +}); +``` + + + +```go +import ( + "github.com/supertokens/supertokens-golang/recipe/thirdparty" + "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" +) + +func main() { + // Inside supertokens.Init + thirdparty.Init(&tpmodels.TypeInput{ + // highlight-start + SignInAndUpFeature: tpmodels.TypeInputSignInAndUp{ + Providers: []tpmodels.ProviderInput{ + // We have provided you with development keys which you can use for testing. + // IMPORTANT: Please replace them with your own OAuth keys for production use. + { + Config: tpmodels.ProviderConfig{ + ThirdPartyId: "google", + Clients: []tpmodels.ProviderClientConfig{ + { + ClientID: "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", + ClientSecret: "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", + }, + }, + }, + }, + { + Config: tpmodels.ProviderConfig{ + ThirdPartyId: "github", + Clients: []tpmodels.ProviderClientConfig{ + { + ClientID: "467101b197249757c71f", + ClientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd", + }, + }, + }, + }, + { + Config: tpmodels.ProviderConfig{ + ThirdPartyId: "apple", + Clients: []tpmodels.ProviderClientConfig{ + { + ClientID: "4398792-io.supertokens.example.service", + AdditionalConfig: map[string]interface{}{ + "keyId": "7M48Y4RYDL", + "privateKey": "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----", + "teamId": "YWQCXGJRJL", + }, + }, + }, + }, + }, + }, + }, + // highlight-end + }) +} +``` + + + +```python +from supertokens_python.recipe.thirdparty.provider import ProviderInput, ProviderConfig, ProviderClientConfig +from supertokens_python.recipe import thirdparty + +# Inside init +thirdparty.init( + # highlight-start + sign_in_and_up_feature=thirdparty.SignInAndUpFeature(providers=[ + # We have provided you with development keys which you can use for testing. + # IMPORTANT: Please replace them with your own OAuth keys for production use. + ProviderInput( + config=ProviderConfig( + third_party_id="google", + clients=[ + ProviderClientConfig( + client_id="1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", + client_secret="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", + ), + ], + ), + ), + ProviderInput( + config=ProviderConfig( + third_party_id="github", + clients=[ + ProviderClientConfig( + client_id="467101b197249757c71f", + client_secret="e97051221f4b6426e8fe8d51486396703012f5bd", + ) + ], + ), + ), + ProviderInput( + config=ProviderConfig( + third_party_id="apple", + clients=[ + ProviderClientConfig( + client_id="io.supertokens.example.service", + additional_config={ + "keyId": "7M48Y4RYDL", + "privateKey": "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----", + "teamId": "YWQCXGJRJL" + }, + ), + ], + ), + ), + ]) + # highlight-end +) +``` + + + + +**When you want to generate your own keys**, please refer to the corresponding documentation to get your client ids and client secrets for each of the below providers: + +
      +Google + +- Generate your client ID and secret by following the [docs here](https://support.google.com/cloud/answer/6158849?hl=en) +- Set the authorisation callback URL to `^{form_websiteDomain}^{form_websiteBasePathForCallbacks}/callback/google` + +
      + +
      +Github + +- Generate your client ID and secret by following the [docs here](https://docs.github.com/en/developers/apps/creating-an-oauth-app) +- Set the authorisation callback URL to `^{form_websiteDomain}^{form_websiteBasePathForCallbacks}/callback/github` + +
      + +
      +Apple + +- Generate your client ID and secret by following [this article](https://medium.com/identity-beyond-borders/how-to-configure-sign-in-with-apple-77c61e336003) +- Set the authorisation callback URL to `^{form_apiDomain}^{form_apiBasePathForCallbacks}/callback/apple`. Note that Apple doesn't allow `localhost` in the URL. So if you are in dev mode, you can use the dev keys we have provided above. + +
      + +:::important +You can find the list of built in providers [here](../common-customizations/sign-in-and-up/built-in-providers). To add a provider that is not listed, you can follow our guide on [setting up custom providers](../common-customizations/sign-in-and-up/custom-providers). +::: + +
      + +## 4. Add the SuperTokens APIs and Configure CORS + + + + + +Now that the SDK is initialized you need to expose the endpoints that will be used by the frontend SDKs. +Besides this, your server's CORS, Cross-Origin Resource Sharing, settings should be updated to allow the use of the authentication headers required by **SuperTokens**. + + + + + + + + +:::important +- Add the `middleware` BEFORE all your routes. +- Add the `cors` middleware BEFORE the SuperTokens middleware as shown below. +::: + +```tsx +import express from "express"; +import cors from "cors"; +import supertokens from "supertokens-node"; +import { middleware } from "supertokens-node/framework/express"; + +let app = express(); + +app.use(cors({ + // highlight-start + origin: "^{form_websiteDomain}", + allowedHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], + credentials: true, + // highlight-end +})); + +// IMPORTANT: CORS should be before the below line. +// highlight-next-line +app.use(middleware()); + +// ...your API routes +``` + + + + +Register the `plugin`. + +```tsx +import Hapi from "@hapi/hapi"; +import supertokens from "supertokens-node"; +import { plugin } from "supertokens-node/framework/hapi"; + +let server = Hapi.server({ + port: 8000, + routes: { + // highlight-start + cors: { + origin: ["^{form_websiteDomain}"], + additionalHeaders: [...supertokens.getAllCORSHeaders()], + credentials: true, + } + // highlight-end + } +}); + +(async () => { + // highlight-next-line + await server.register(plugin); + + await server.start(); +})(); + +// ...your API routes +``` + + + + +Register the `plugin`. Also register [`@fastify/formbody`](https://github.com/fastify/fastify-formbody) plugin. + +```tsx +import cors from "@fastify/cors"; +import supertokens from "supertokens-node"; +import { plugin } from "supertokens-node/framework/fastify"; +import formDataPlugin from "@fastify/formbody"; + +import fastifyImport from "fastify"; + +let fastify = fastifyImport(); + +// ...other middlewares +// highlight-start +fastify.register(cors, { + origin: "^{form_websiteDomain}", + allowedHeaders: ['Content-Type', ...supertokens.getAllCORSHeaders()], + credentials: true, +}); +// highlight-end + +(async () => { + // highlight-next-line + await fastify.register(formDataPlugin); + // highlight-next-line + await fastify.register(plugin); + + await fastify.listen(8000); +})(); + +// ...your API routes +``` + + + + +:::important +Add the `middleware` BEFORE all your routes. +::: + +```tsx +import Koa from "koa"; +import cors from '@koa/cors'; +import supertokens from "supertokens-node"; +import { middleware } from "supertokens-node/framework/koa"; + +let app = new Koa(); + +app.use(cors({ + // highlight-start + origin: "^{form_websiteDomain}", + allowHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], + credentials: true, + // highlight-end +})); + +// highlight-next-line +app.use(middleware()); + +// ...your API routes +``` + + + + +:::important +Add the `middleware` BEFORE all your routes. +::: + +```tsx +import { RestApplication } from "@loopback/rest"; +import supertokens from "supertokens-node"; +import { middleware } from "supertokens-node/framework/loopback"; + +let app = new RestApplication({ + rest: { + cors: { + // highlight-start + origin: "^{form_websiteDomain}", + allowedHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], + credentials: true + // highlight-end + } + } +}); + +// highlight-next-line +app.middleware(middleware); + +// ...your API routes +``` + + + + + + + + +Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. + +```go +import ( + "net/http" + "strings" + + "github.com/supertokens/supertokens-golang/supertokens" +) + +func main() { + // SuperTokens init... + + //highlight-start + http.ListenAndServe("SERVER ADDRESS", corsMiddleware( + supertokens.Middleware(http.HandlerFunc(func(rw http.ResponseWriter, + //highlight-end + r *http.Request) { + // TODO: Handle your APIs.. + + })))) +} + +func corsMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(response http.ResponseWriter, r *http.Request) { + response.Header().Set("Access-Control-Allow-Origin", "^{form_websiteDomain}") + response.Header().Set("Access-Control-Allow-Credentials", "true") + if r.Method == "OPTIONS" { + // we add content-type + other headers used by SuperTokens + response.Header().Set("Access-Control-Allow-Headers", + strings.Join(append([]string{"Content-Type"}, + //highlight-start + supertokens.GetAllCORSHeaders()...), ",")) + //highlight-end + response.Header().Set("Access-Control-Allow-Methods", "*") + response.Write([]byte("")) + } else { + next.ServeHTTP(response, r) + } + }) +} +``` + + + + +Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. + +```go +import ( + "net/http" + + "github.com/gin-contrib/cors" + "github.com/gin-gonic/gin" + "github.com/supertokens/supertokens-golang/supertokens" +) + +func main() { + // SuperTokens init... + + router := gin.New() + + // CORS + router.Use(cors.New(cors.Config{ + AllowOrigins: []string{"^{form_websiteDomain}"}, + AllowMethods: []string{"GET", "POST", "DELETE", "PUT", "OPTIONS"}, + AllowHeaders: append([]string{"content-type"}, + // highlight-next-line + supertokens.GetAllCORSHeaders()...), + AllowCredentials: true, + })) + + // Adding the SuperTokens middleware + // highlight-start + router.Use(func(c *gin.Context) { + supertokens.Middleware(http.HandlerFunc( + func(rw http.ResponseWriter, r *http.Request) { + c.Next() + })).ServeHTTP(c.Writer, c.Request) + // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly + c.Abort() + }) + // highlight-end + + // Add APIs and start server +} +``` + + + + +Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. + +```go +import ( + "github.com/go-chi/chi" + "github.com/go-chi/cors" + "github.com/supertokens/supertokens-golang/supertokens" +) + +func main() { + // SuperTokens init... + + r := chi.NewRouter() + + // CORS + r.Use(cors.Handler(cors.Options{ + AllowedOrigins: []string{"^{form_websiteDomain}"}, + AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, + AllowedHeaders: append([]string{"Content-Type"}, + //highlight-next-line + supertokens.GetAllCORSHeaders()...), + AllowCredentials: true, + })) + + // SuperTokens Middleware + //highlight-next-line + r.Use(supertokens.Middleware) + + // Add APIs and start server +} +``` + + + +Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. + +```go +import ( + "net/http" + + "github.com/gorilla/handlers" + "github.com/gorilla/mux" + "github.com/supertokens/supertokens-golang/supertokens" +) + +func main() { + // SuperTokens init... + + // TODO: Add APIs + + router := mux.NewRouter() + + // Adding handlers.CORS(options)(supertokens.Middleware(router))) + //highlight-start + http.ListenAndServe("SERVER ADDRESS", handlers.CORS( + handlers.AllowedHeaders(append([]string{"Content-Type"}, + supertokens.GetAllCORSHeaders()...)), + handlers.AllowedMethods([]string{"GET", "POST", "PUT", "HEAD", "OPTIONS"}), + handlers.AllowedOrigins([]string{"^{form_websiteDomain}"}), + handlers.AllowCredentials(), + )(supertokens.Middleware(router))) + //highlight-end +} +``` + + + + + + + + + +Use the `Middleware` (**BEFORE all your routes**) and the `get_all_cors_headers()` functions as shown below. + +```python +from supertokens_python import get_all_cors_headers +from fastapi import FastAPI +from starlette.middleware.cors import CORSMiddleware +from supertokens_python.framework.fastapi import get_middleware + +app = FastAPI() +# highlight-next-line +app.add_middleware(get_middleware()) + +# TODO: Add APIs + +app.add_middleware( + CORSMiddleware, + allow_origins=[ + "^{form_websiteDomain}" + ], + allow_credentials=True, + allow_methods=["GET", "PUT", "POST", "DELETE", "OPTIONS", "PATCH"], + # highlight-next-line + allow_headers=["Content-Type"] + get_all_cors_headers(), +) + +# TODO: start server +``` + + + + +- Use the `Middleware` (**BEFORE all your routes and after calling init function**) and the `get_all_cors_headers()` functions as shown below. +- Add a route to catch all paths and return a 404. This is needed because if we don't add this, then OPTIONS request for the APIs exposed by the `Middleware` will return a `404`. + +```python +from supertokens_python import get_all_cors_headers +from flask import Flask, abort +from flask_cors import CORS # type: ignore +from supertokens_python.framework.flask import Middleware + +app = Flask(__name__) +# highlight-next-line +Middleware(app) + +# TODO: Add APIs + +CORS( + app=app, + origins=[ + "^{form_websiteDomain}" + ], + supports_credentials=True, + # highlight-next-line + allow_headers=["Content-Type"] + get_all_cors_headers(), +) + +# This is required since if this is not there, then OPTIONS requests for +# the APIs exposed by the supertokens' Middleware will return a 404 +# highlight-start +@app.route('/', defaults={'u_path': ''}) # type: ignore +@app.route('/') # type: ignore +def catch_all(u_path: str): + abort(404) +# highlight-end + +# TODO: start server +``` + + + + +Use the `Middleware` and the `get_all_cors_headers()` functions as shown below in your `settings.py`. + +```python +from supertokens_python import get_all_cors_headers +from typing import List +from corsheaders.defaults import default_headers + +CORS_ORIGIN_WHITELIST = [ + "^{form_websiteDomain}" +] + +CORS_ALLOW_CREDENTIALS = True + +CORS_ALLOWED_ORIGINS = [ + "^{form_websiteDomain}" +] + +CORS_ALLOW_HEADERS: List[str] = list(default_headers) + [ + "Content-Type" + # highlight-next-line +] + get_all_cors_headers() + +INSTALLED_APPS = [ + 'corsheaders', + 'supertokens_python' +] + +MIDDLEWARE = [ + 'corsheaders.middleware.CorsMiddleware', + ..., + # highlight-next-line + 'supertokens_python.framework.django.django_middleware.middleware', +] +# TODO: start server +``` + + + + + + + + + + + +You can review all the endpoints that are added through the use of **SuperTokens** by visiting the [API Specs](https://app.swaggerhub.com/apis/supertokens/FDI). + + + + + +## 5. Add the SuperTokens Error Handler + + + + + + + +Depending on the language and framework that you are using, you might need to add a custom error handler to your server. +The handler will catch all the authentication related errors and return proper HTTP responses that can be parsed by the frontend SDKs. + + + + + + +```tsx +import express, { Request, Response, NextFunction } from 'express'; +import { errorHandler } from "supertokens-node/framework/express"; + +let app = express(); + +// ...your API routes + +// highlight-start +// Add this AFTER all your routes +app.use(errorHandler()) +// highlight-end + +// your own error handler +app.use((err: unknown, req: Request, res: Response, next: NextFunction) => { /* ... */ }); +``` + + + +No additional `errorHandler` is required. + + + + +Add the `errorHandler` **Before all your routes and plugin registration** + +```tsx +import Fastify from "fastify"; +import { errorHandler } from "supertokens-node/framework/fastify"; + +let fastify = Fastify(); + +// highlight-next-line +fastify.setErrorHandler(errorHandler()); + +// ...your API routes +``` + + + +No additional `errorHandler` is required. + + + + +No additional `errorHandler` is required. + + + + + + +:::info +You can skip this step +::: + + + + +:::info +You can skip this step +::: + + + + + + +## 6. Secure Application Routes + +Now that your server can authenticate users, the final step that you need to take care of is to prevent unauthorized access to certain parts of the application. + + + + +For your APIs that require a user to be logged in, use the `verifySession` middleware. + + + + + +```tsx +import express from "express"; +import { verifySession } from "supertokens-node/recipe/session/framework/express"; +import { SessionRequest } from "supertokens-node/framework/express"; + +let app = express(); + +// highlight-start +app.post("/like-comment", verifySession(), (req: SessionRequest, res) => { + let userId = req.session!.getUserId(); + // highlight-end + //.... +}); +``` + + + + +```tsx +import Hapi from "@hapi/hapi"; +import { verifySession } from "supertokens-node/recipe/session/framework/hapi"; +import { SessionRequest } from "supertokens-node/framework/hapi"; + +let server = Hapi.server({ port: 8000 }); + +server.route({ + path: "/like-comment", + method: "post", + //highlight-start + options: { + pre: [ + { + method: verifySession() + }, + ], + }, + handler: async (req: SessionRequest, res) => { + let userId = req.session!.getUserId(); + //highlight-end + //... + } +}) +``` + + + +```tsx +import Fastify from "fastify"; +import { verifySession } from "supertokens-node/recipe/session/framework/fastify"; +import { SessionRequest } from "supertokens-node/framework/fastify"; + +let fastify = Fastify(); + +//highlight-start +fastify.post("/like-comment", { + preHandler: verifySession(), +}, (req: SessionRequest, res) => { + let userId = req.session!.getUserId(); + //highlight-end + //.... +}); +``` + + + + +```tsx +import KoaRouter from "koa-router"; +import { verifySession } from "supertokens-node/recipe/session/framework/koa"; +import { SessionContext } from "supertokens-node/framework/koa"; + +let router = new KoaRouter(); + +//highlight-start +router.post("/like-comment", verifySession(), (ctx: SessionContext, next) => { + let userId = ctx.session!.getUserId(); + //highlight-end + //.... +}); +``` + + + + +```tsx +import { inject, intercept } from "@loopback/core"; +import { RestBindings, MiddlewareContext, post, response } from "@loopback/rest"; +import { verifySession } from "supertokens-node/recipe/session/framework/loopback"; +import { SessionContext } from "supertokens-node/framework/loopback"; + +class LikeComment { + //highlight-start + constructor(@inject(RestBindings.Http.CONTEXT) private ctx: MiddlewareContext) { } + @post("/like-comment") + @intercept(verifySession()) + @response(200) + handler() { + let userId = (this.ctx as SessionContext).session!.getUserId(); + //highlight-end + //.... + } +} +``` + + + + + + +For your APIs that require a user to be logged in, use the `VerifySession` middleware. + + + + +```go +import ( + "fmt" + "net/http" + + "github.com/supertokens/supertokens-golang/recipe/session" +) + +func main() { + _ = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + // Wrap the API handler in session.VerifySession + session.VerifySession(nil, likeCommentAPI).ServeHTTP(rw, r) + }) +} + +func likeCommentAPI(w http.ResponseWriter, r *http.Request) { + // retrieve the session object as shown below + sessionContainer := session.GetSessionFromRequestContext(r.Context()) + + userID := sessionContainer.GetUserID() + + fmt.Println(userID) +} +``` + + + + +```go +import ( + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/supertokens/supertokens-golang/recipe/session" + "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" +) + +func main() { + router := gin.New() + + // Wrap the API handler in session.VerifySession + router.POST("/likecomment", verifySession(nil), likeCommentAPI) +} + +// This is a function that wraps the supertokens verification function +// to work the gin +func verifySession(options *sessmodels.VerifySessionOptions) gin.HandlerFunc { + return func(c *gin.Context) { + session.VerifySession(options, func(rw http.ResponseWriter, r *http.Request) { + c.Request = c.Request.WithContext(r.Context()) + c.Next() + })(c.Writer, c.Request) + // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly + c.Abort() + } +} + +func likeCommentAPI(c *gin.Context) { + // retrieve the session object as shown below + sessionContainer := session.GetSessionFromRequestContext(c.Request.Context()) + + userID := sessionContainer.GetUserID() + + fmt.Println(userID) +} +``` + + + + +```go +import ( + "fmt" + "net/http" + + "github.com/go-chi/chi" + "github.com/supertokens/supertokens-golang/recipe/session" +) + +func main() { + r := chi.NewRouter() + + // Wrap the API handler in session.VerifySession + r.Post("/likecomment", session.VerifySession(nil, likeCommentAPI)) +} + +func likeCommentAPI(w http.ResponseWriter, r *http.Request) { + // retrieve the session object as shown below + sessionContainer := session.GetSessionFromRequestContext(r.Context()) + + userID := sessionContainer.GetUserID() + + fmt.Println(userID) +} +``` + + + + +```go +import ( + "fmt" + "net/http" + + "github.com/gorilla/mux" + "github.com/supertokens/supertokens-golang/recipe/session" +) + +func main() { + router := mux.NewRouter() + + // Wrap the API handler in session.VerifySession + router.HandleFunc("/likecomment", session.VerifySession(nil, likeCommentAPI)).Methods(http.MethodPost) +} + +func likeCommentAPI(w http.ResponseWriter, r *http.Request) { + // retrieve the session object as shown below + sessionContainer := session.GetSessionFromRequestContext(r.Context()) + + userID := sessionContainer.GetUserID() + + fmt.Println(userID) +} +``` + + + + + + +For your APIs that require a user to be logged in, use the `verify_session` middleware. + + + + +```python +from supertokens_python.recipe.session.framework.fastapi import verify_session +from supertokens_python.recipe.session import SessionContainer +from fastapi import Depends + +# highlight-start +@app.post('/like_comment') # type: ignore +async def like_comment(session: SessionContainer = Depends(verify_session())): + user_id = session.get_user_id() + # highlight-end + + print(user_id) +``` + + + + +```python +from supertokens_python.recipe.session.framework.flask import verify_session +from supertokens_python.recipe.session import SessionContainer +from flask import g + +# highlight-start +@app.route('/update-jwt', methods=['POST']) # type: ignore +@verify_session() +def like_comment(): + session: SessionContainer = g.supertokens # type: ignore + + user_id = session.get_user_id() + # highlight-end + + print(user_id) +``` + + + + +```python +from supertokens_python.recipe.session.framework.django.asyncio import verify_session +from django.http import HttpRequest +from supertokens_python.recipe.session import SessionContainer + +# highlight-start +@verify_session() +async def like_comment(request: HttpRequest): + session: SessionContainer = request.supertokens # type: ignore + + user_id = session.get_user_id() + # highlight-end + + print(user_id) +``` + + + + + + + +The middleware function returns a `401` to the frontend if a session doesn't exist, or if the access token has expired, in which case, our frontend SDK automatically refreshes the session. + +In case of successful session verification, you get access to a `session` object using which you can get the user's ID, or manipulate the session information. + +## 7. Test the Login Flow + +Now that you have configured both the frontend and the backend, you can return to the frontend login page. +From here follow these steps to confirm that your setup is working properly. +- Click on the **Sign up** button to create a new account. +- After you have created the account go to **Login** page and fill in your credentials. +- If you are greeted with the login screen you have completed the quickstart setup. + +:::success 🎉 Congratulations 🎉 + +You've successfully integrated **SuperTokens** with your existing application! + +Of course, there are additional things that you should add in order to provide a complete authentication experience. +We will talk about those things in the [next section](./next-steps). + +::: diff --git a/v2/thirdparty/quickstart/frontend-setup.mdx b/v2/thirdparty/quickstart/frontend-setup.mdx new file mode 100644 index 000000000..6b20d612e --- /dev/null +++ b/v2/thirdparty/quickstart/frontend-setup.mdx @@ -0,0 +1,903 @@ +--- +id: frontend-setup +title: Frontend Setup +hide_title: true +show_ui_switcher: true +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" +import WebJsInjector from "/src/components/webJsInjector" + +import FrontendCustomUISDKInstall from '../../community/reusableMD/custom-ui/frontent-custom-ui-sdk-install.mdx' +import FrontendCustomUISessionTokens from '../../community/reusableMD/custom-ui/frontend-custom-ui-session-tokens.mdx' +import FrontendCustomUISessionManagement from '../../community/reusableMD/custom-ui/frontend-custom-ui-session-management.mdx' +import FrontendCustomUISignout from '../../community/reusableMD/custom-ui/frontend-custom-ui-signout.mdx' +import FrontendCustomUIThirdParty from '../../community/reusableMD/custom-ui/frontend-custom-ui-thirdparty.mdx' +import FrontendSDKInstall from "../../community/reusableMD/frontend-sdk-install.mdx" + + +import {CustomUILink, PrebuiltUILink, PreBuiltOrCustomUISwitcher, PreBuiltUIContent, CustomUIContent} from "/src/components/preBuiltOrCustomUISwitcher" +import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" +import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" +import PreBuiltUIInjector from "/src/components/prebuiltuiInjector" +import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; +import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" +import {Question, Answer}from "/src/components/question" +import RRDVersionSubTabs from "/src/components/tabs/RRDVersionSubTabs" +import AppInfoForm from "/src/components/appInfoForm" +import NodeJSFrameworkSubTabsServerless from "/src/components/tabs/NodeJSFrameworkSubTabsServerless" +import NpmVersionOrYarnSubTabs from "/src/components/tabs/NpmVersionOrYarnSubTabs" +import CoreInjector from "/src/components/coreInjector" +import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs" +import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" +import BackendSDKCasing from "/src/components/BackendSDKCasing" + +# Frontend Setup + +Start the setup by configuring your frontend application to use **SuperTokens** for authentication. + + + + + +This guide uses the **SuperTokens Pre Built UI** components. +If you want to create your own interface please check the **Custom UI** tutorial. + +## 1. Install the SDK + + + + + +Run the following command in your terminal to install the package. + + + + + +## 2. Initialize the SDK + + + + + + + + + + + +In your main application file call the `SuperTokens.init` function to initialize the SDK. +The `init` call includes the [main configuration details](../appinfo), as well as the **recipes** that you will be using in your setup. +After that you will have to wrap the application with the `SuperTokensWrapper` component. +This will provide authentication context for the rest of the UI tree. + + + +Based on your setup, edit the `ThirdParty` recipe `init` call and remove the providers you don't want to use. + +```tsx +import React from 'react'; + +// highlight-start +import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; +import ThirdParty, {Github, Google, Facebook, Apple} from "supertokens-auth-react/recipe/thirdparty"; +import Session from "supertokens-auth-react/recipe/session"; + +SuperTokens.init({ + appInfo: { + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + websiteDomain: "^{form_websiteDomain}", + apiBasePath: "^{form_apiBasePath}", + websiteBasePath: "^{form_websiteBasePath}" + }, + recipeList: [ + ThirdParty.init({ + signInAndUpFeature: { + providers: [ + Github.init(), + Google.init(), + Facebook.init(), + Apple.init(), + ] + } + }), + Session.init() + ] +}); +// highlight-end + + +/* Your App */ +class App extends React.Component { + render() { + return ( + // highlight-next-line + + {/*Your app components*/} + // highlight-next-line + + ); + } +} +``` + + + + + + + + + + + +Before we initialize the `supertokens-web-js` SDK let's see how we will use it in our Angular app + +**Architecture** + +- The `supertokens-web-js` SDK is responsible for session management and providing helper functions to check if a session exists, or validate the access token claims on the frontend (for example, to check for user roles before showing some UI). We will initialise this SDK on the root of your Angular app, so that all pages in your app can use it. +- We will create a `^{form_websiteBasePath}*` route in the Angular app which will render our pre built UI which will also need to be initialised, but only on that route. + +**Creating the `^{form_websiteBasePath}` route** + +- Use the Angular CLI to generate a new route + + ```bash + ng generate module auth --route auth --module app.module + ``` + +- Add the following code to your `auth` angular component + + ```tsx title="/app/auth/auth.component.ts" + import {init as supertokensUIInit} from "supertokens-auth-react-script"; + import supertokensUIThirdParty from "supertokens-auth-react-script/recipe/thirdparty"; + import supertokensUISession from "supertokens-auth-react-script/recipe/session"; + 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: [ + supertokensUIThirdParty.init({ + signInAndUpFeature: { + providers: [ + supertokensUIThirdParty.Github.init(), + supertokensUIThirdParty.Google.init(), + supertokensUIThirdParty.Facebook.init(), + supertokensUIThirdParty.Apple.init(), + ] + } + }), + supertokensUISession.init(), + ], + }); + } + this.renderer.appendChild(this.document.body, script); + } + } + ``` + - In the `loadScript` function, we provide the SuperTokens config for the UI. We add the session and social login recipes along with Github, Google, Facebook and Apple login buttons. + +- Initialize the `supertokens-web-js` SDK in your angular app's root component. This will provide session management across your entire application. + + ```tsx title="/app/app.component.ts " + import SuperTokens from "supertokens-web-js"; + import Session from "supertokens-web-js/recipe/session"; + + SuperTokens.init({ + appInfo: { + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}", + }, + recipeList: [ + Session.init(), + ], + }); + ``` + +
      + +
      + +
      + + + + + + + +Before we initialize the `supertokens-web-js` SDK let's see how we will use it in our Vue app + +**Architecture** + +- The `supertokens-web-js` SDK is responsible for session management and providing helper functions to check if a session exists, or validate the access token claims on the frontend (for example, to check for user roles before showing some UI). We will initialise this SDK on the root of your Vue app, so that all pages in your app can use it. +- We will create a `^{form_websiteBasePath}*` route in the Vue app which will render our pre built UI which will also need to be initialised, but only on that route. + +**Creating the `^{form_websiteBasePath}` route** + +- Create a new file `AuthView.vue`, this Vue component will be used to render the auth component: + ```tsx + import {init as supertokensUIInit} from "supertokens-auth-react-script"; + import supertokensUIThirdParty from "supertokens-auth-react-script/recipe/thirdparty"; + import supertokensUISession from "supertokens-auth-react-script/recipe/session"; + + + + ``` + - In the `loadScript` function, we provide the SuperTokens config for the UI. We add the session and social login recipes along with Github, Google, Facebook and Apple login buttons. + +- Initialize the `supertokens-web-js` SDK in your Vue app's `main.ts` file. This will provide session management across your entire application. + + ```tsx title="/main.ts " + // @ts-ignore + import { createApp } from "vue"; + import SuperTokens from "supertokens-web-js"; + import Session from "supertokens-web-js/recipe/session"; + // @ts-ignore + import App from "./App.vue"; + // @ts-ignore + import router from "./router"; + + SuperTokens.init({ + appInfo: { + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}", + }, + recipeList: [ + Session.init(), + ], + }); + + const app = createApp(App); + + app.use(router); + + app.mount("#app"); + + ``` + + + + + + + + + +:::important +SuperTokens does not support pre-built UI for mobile frameworks. Please toggle the **Custom UI** option from the *Table of Contents* section. +::: + + + +
      + +## 3. Configure Routing + + + + + + + + +In order for the **Pre Built UI** to be rendered inside your application, will will have to specify which routes will show the authentication components. +The **React SDK** uses [**React Router**](https://reactrouter.com/en/main) under the hood to achieve this. +Based on whether you already use this package or not in your project, there are two different ways of configuring the routes. + + + + + + + + + +Call the `getSuperTokensRoutesForReactRouterDom` method from within any `react-router-dom` `Routes` component. + +```tsx +import React from 'react'; +import { + BrowserRouter, + Routes, + Route, + Link +} from "react-router-dom"; + +// highlight-next-line +import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; +import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui"; +^{prebuiltuiimport} +import * as reactRouterDom from "react-router-dom"; + +class App extends React.Component { + render() { + return ( + + + + {/*This renders the login UI on the ^{form_websiteBasePath} route*/} + // highlight-next-line + {getSuperTokensRoutesForReactRouterDom(reactRouterDom, [^{recipePreBuiltUINameCapitalLetters}])} + {/*Your app routes*/} + + + + ); + } +} +``` + + + + + +Call the `getSuperTokensRoutesForReactRouterDom` method from within any `react-router-dom` `Switch` component. + +```tsx +import React from 'react'; +import { + BrowserRouter, + Switch, + Route, + Link +} from "react-router-dom5"; + +// highlight-next-line +import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; +import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui"; +^{prebuiltuiimport} +import * as reactRouterDom from "react-router-dom"; + + +class App extends React.Component { + render() { + return ( + + + + {/*This renders the login UI on the ^{form_websiteBasePath} route*/} + // highlight-next-line + {getSuperTokensRoutesForReactRouterDom(reactRouterDom, [^{recipePreBuiltUINameCapitalLetters}])} + {/*Your app routes*/} + + + + ); + } +} +``` + + + + + + + + +Add the highlighted code snippet to your root level `render` function. + +```tsx +import React from 'react'; +^{prebuiltuiimport} +import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; +import { canHandleRoute, getRoutingComponent } from "supertokens-auth-react/ui"; + +class App extends React.Component { + render() { + // highlight-start + if (canHandleRoute([^{recipePreBuiltUINameCapitalLetters}])) { + // This renders the login UI on the ^{form_websiteBasePath} route + return getRoutingComponent([^{recipePreBuiltUINameCapitalLetters}]) + } + // highlight-end + + return ( + {/*Your app*/} + ); + } + +} +``` + + + + + + + + + + +Update your angular router so that all auth related requests load the `auth` component + +```tsx title="/app/app-routing.module.ts" +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +const routes: Routes = [ + // highlight-start + { + path: "^{form_websiteBasePath_withoutForwardSlash}", + // @ts-ignore + loadChildren: () => import("./auth/auth.module").then((m) => m.AuthModule), + }, + + // @ts-ignore + { path: "**", loadChildren: () => import("./home/home.module").then((m) => m.HomeModule) }, + // highlight-end +]; + +@NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule], +}) +export class AppRoutingModule {} +``` + + + + + + + + +Update your Vue router so that all auth related requests load the `AuthView` component + +```tsx title="/router/index.ts" +// @ts-ignore +import { createRouter, createWebHistory } from "vue-router"; +// @ts-ignore +import HomeView from "../views/HomeView.vue"; +// @ts-ignore +import AuthView from "../views/AuthView.vue"; + +const router = createRouter({ + // @ts-ignore + history: createWebHistory(import.meta.env.BASE_URL), + routes: [ + { + path: "/", + name: "home", + component: HomeView, + }, + { + path: "^{form_websiteBasePath}/:pathMatch(.*)*", + name: "auth", + component: AuthView, + }, + ], +}); + +export default router; +``` + + + + + + + +:::important +SuperTokens does not support pre-built UI for mobile frameworks. Please toggle the **Custom UI** option from the *Table of Contents* section. +::: + + + + + + + +## 4. Handle Session Tokens + + + + + +This part is handled automatically by the **Frontend SDK**. +You don not need to do anything. +The step serves more as a way for us to tell you how is this handled under the hood. + +After you call the `init` function, the **SDK** will add interceptors to both `fetch` and `XHR`, XMLHTTPRequest. The latter is used by the `axios` library. +The interceptors save the session tokens that are generated from the authentication flow. +Those tokens are then added to requests initialized by your frontend app which target the backend API. +By default, the tokens are stored through session cookies but you can also switch to [header based authentication](../common-customizations/sessions/token-transfer-method). + + + +## 5. Secure Application Routes + +In order to prevent unauthorized access to ceratain parts of your frontend application you can use our utilities. +Follow the code samples below to understand how to do this. + + + + + +You can wrap your components with the `` react component. This will ensure that your component renders only if the user is logged in. If they are not logged in, the user will be redirected to the login page. + +```tsx +import React from "react"; +import { + BrowserRouter, + Routes, + Route, +} from "react-router-dom"; +// highlight-next-line +import { SessionAuth } from "supertokens-auth-react/recipe/session"; +// @ts-ignore +import MyDashboardComponent from "./dashboard"; + +class App extends React.Component { + render() { + return ( + + + + {/*Components that require to be protected by authentication*/} + + + // highlight-end + } /> + + + ); + } +} +``` + + + + + +You can use the `doesSessionExist` function to check if a session exists in all your routes. + +```tsx +import Session from 'supertokens-web-js/recipe/session'; + +async function doesSessionExist() { + if (await Session.doesSessionExist()) { + // user is logged in + } else { + // user has not logged in yet + } +} +``` + + + + + +## 6. View the login UI + + + +You can check the login UI by visiting the `^{form_websiteBasePath}` route, in your frontend application. +To review all the components of our pre-built UI please follow [this link](https://master--6571be2867f75556541fde98.chromatic.com/?path=/story/auth-page--playground). + + + + + +
      + + + +This guide shows you how to create your own UI on top of the **SuperTokens SDK**. +If you want to use our **Pre Built Components** please check the following tutorial. + +## 1. Install the SDK + + + +## 2. Initialize the SDK + +Call the SDK init function at the start of your application. +The invocation includes the [main configuration details](../appinfo), as well as the **recipes** that you will be using in your setup. + + + + + + + + + +```tsx +import SuperTokens from 'supertokens-web-js'; +import Session from 'supertokens-web-js/recipe/session'; +import ^{recipeNameCapitalLetters} from 'supertokens-web-js/recipe/^{codeImportRecipeName}' + +SuperTokens.init({ + appInfo: { + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}", + appName: "...", + }, + recipeList: [ + Session.init(), + ^{recipeNameCapitalLetters}.init(), + ], +}); +``` + + + + + + + + + +```tsx +import supertokens from "supertokens-web-js-script"; +import supertokensSession from "supertokens-web-js-script/recipe/session"; +import supertokens^{recipeNameCapitalLetters} from 'supertokens-web-js-script/recipe/^{codeImportRecipeName}' +supertokens.init({ + appInfo: { + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}", + appName: "...", + }, + recipeList: [ + supertokensSession.init(), + supertokens^{recipeNameCapitalLetters}.init(), + ], +}); +``` + + + + + + + + + + + + + + + + + +```tsx +import SuperTokens from 'supertokens-react-native'; + +SuperTokens.init({ + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}", +}); +``` + + + + + + + + + +Add the `SuperTokens.init` function call at the start of your application. + +```kotlin +import android.app.Application +import com.supertokens.session.SuperTokens + +class MainApplication: Application() { + override fun onCreate() { + super.onCreate() + + SuperTokens.Builder(this, "^{form_apiDomain}") + .apiBasePath("^{form_apiBasePath}") + .build() + } +} +``` + + + + + + + + + + +```swift +import UIKit +import SuperTokensIOS + +fileprivate class ApplicationDelegate: UIResponder, UIApplicationDelegate { + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + do { + try SuperTokens.initialize( + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}" + ) + } catch SuperTokensError.initError(let message) { + // TODO: Handle initialization error + } catch { + // Some other error + } + + return true + } + +} +``` + + + + + + + + + +```dart +import 'package:supertokens_flutter/supertokens.dart'; + +void main() { + SuperTokens.init( + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}", + ); +} +``` + + + + + + + + + + + + +## 3. Add the Login UI + + + +## 4. Handle Session Tokens + + + +## 5. Protect Frontend Routes + + + +## 6. Add a Sign Out Action + + + + + + + +
      + + +:::success 🎉 Congratulations 🎉 + +Congratulations! You've successfully integrated your frontend app with SuperTokens. + +The [next section](./backend-setup) will guide you through setting up your backend and then you should be able to complete a login flow. + +::: + diff --git a/v2/thirdparty/quickstart/introduction.mdx b/v2/thirdparty/quickstart/introduction.mdx new file mode 100644 index 000000000..3c3a16d53 --- /dev/null +++ b/v2/thirdparty/quickstart/introduction.mdx @@ -0,0 +1,85 @@ +--- +id: introduction +title: Introduction +hide_title: true +show_ui_switcher: false +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + +# Introduction + +## Overview + +This quickstart will guide you through how to set up a basic project that uses **SuperTokens** to authenticate users. +The tutorial shows a **Thirdparty Login** flow, rendered by either our **Prebuilt UI components** or by your own **Custom UI**. + + + + + +If you want to skip straight to an example application you can choose between: +- Checking our live [demo application](https://^{docsLinkRecipeName}.demo.supertokens.com/auth) +- Running a **SuperTokens** project from your local machine. You just have to use our CLI app and execute the following command: + +```bash +npx create-supertokens-app@latest --recipe=^{docsLinkRecipeName} +``` + + + + +## Before you start + + + + + +Before going into the actual tutorial, let's get a clear picture of how **SuperTokens** works and some of the terms that we will use throughout the documentation. + +### SuperTokens Core + +The main service that provides all the functionality is called the **SuperTokens Core**. SDKs communicate over an API with this service in order to +perform authentication related tasks. + +Unlike with other providers, the **SuperTokens Frontend SDK** never talks to the **Authentication Service** directly. +All the requests target your existing **Backend Service**. +From there, the **Backend SDKs** are used to expose new authentication routes. Those in turn communicate with the **SuperTokens Core**. + +You can check the following diagram for a high level overview of how the services will interact within an authentication setup that involves **SuperTokens**. + + + + Flowchart of architecture when using SuperTokens managed service + + + Flowchart of architecture when self-hosting SuperTokens + + + +:::info Edge Cases +- You can also host the **SuperTokens Core** yourself. In that case your backend will communicate with a service that exists inside your infrastructure. +- If you are using a backend for which we do not have an SDK, you will have to spin up an additional auth service in a language for which we do have a backend SDK (NodeJS, Python or Golang). +::: + + +### Recipes + +The functionalities that **SuperTokens** provides are bundled into objects that can be reffered to as **Recipes**. +Everything from *authentication methods* to *session and user management* can be included under this concept. +In the following sections, we will see how recipes get initialised and configured and how you can customise them to fit your use case. + + +Now that we have cleared all this out, we can move forward with the actual tutorial. +Go to the next page to see how to configure your [Frontend Application](./frontend-setup). + + + diff --git a/v2/thirdparty/quickstart/next-steps.mdx b/v2/thirdparty/quickstart/next-steps.mdx new file mode 100644 index 000000000..ddf50b09c --- /dev/null +++ b/v2/thirdparty/quickstart/next-steps.mdx @@ -0,0 +1,178 @@ +--- +id: next-steps +title: Next Steps +hide_title: true +show_ui_switcher: false +show_next_button: false +--- + +import Card from "/src/components/card/Card" +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +import PreBuiltUIInjector from "/src/components/prebuiltuiInjector" +import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; +import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" +import {Question, Answer}from "/src/components/question" +import RRDVersionSubTabs from "/src/components/tabs/RRDVersionSubTabs" +import AppInfoForm from "/src/components/appInfoForm" +import NodeJSFrameworkSubTabsServerless from "/src/components/tabs/NodeJSFrameworkSubTabsServerless" +import CoreInjector from "/src/components/coreInjector" +import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs" +import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" +import BackendSDKCasing from "/src/components/BackendSDKCasing" + + + +# Next Steps + + + + + +## Overview + +Now that you have completed the quickstart guide there are a few more things that you need to take care of on the road towards a production ready authentication experience. + +## Configure the Core Service + +If you have signed up and deployed a SuperTokens environment already, you can skip this step. +Otherwise, please follow these instructions to use the correct **SuperTokens Core** instance in your application. + +The steps show you how to connect to a **SuperTokens Managed Service Environment**. +If you want to self host the core instance please check the [following guide](../pre-built-ui/setup/core/with-docker). + +### 1. Sign up for a SuperTokens account + +Open this [page](https://supertokens.com/auth) in order to access the account creation page. +Select the account that you want to use and wait for the action to complete. + +### 2. Select the authentication method + +After you have created your account, you will be prompted with a form that will ask you to specify details about your configuration. +Select which authentication method that you want to use. + +Integration with SuperTokens SDKs + +### 3. Select the region where you want to deploy the core service# + +SuperTokens environments can be deployed in 3 different regions: `US East (N. Virginia)`, `Europe (Ireland)`, `Asia Pacific (Singapore)`. +In order to avoid any latency issues please select a region that is closest to where your services are hosted. + +### 4. Click the deploy button 🚀 + +Integration with SuperTokens SDKs + +Our internal service will deploy a separate environment based on your selection. + +After this process is complete, you will be directed to the dashboard page. Here you can view and edit information about your newly created environment. + +The main thing that we want to focus is the **Connecting to a development instance** section. +There you can see two different values, the `connectionURI` and the `apiKey`. +You will use these values in the next step. + +:::info + +The initial setup flow only configures a development environment. In order to use SuperTokens in production, you will have to click the Create Production Env button. + +::: + +### 5. Connect the Backend SDK with SuperTokens 🔌 + +Add the `connectionURI` and the `apiKey` shown on the dashboard to your code on the backend. + +SuperTokens managed service dashboard connectionURI and API key + + + + +```tsx +import supertokens from "supertokens-node"; + +supertokens.init({ + // highlight-start + supertokens: { + connectionURI: "", + apiKey: "" + }, + // highlight-end + appInfo: { + apiDomain: "...", + appName: "...", + websiteDomain: "..." + }, + recipeList: [] +}); +``` + + + + +```go +import "github.com/supertokens/supertokens-golang/supertokens" + +func main() { + supertokens.Init(supertokens.TypeInput{ + // highlight-start + Supertokens: &supertokens.ConnectionInfo{ + ConnectionURI: "", + APIKey: "", + }, + // highlight-end + }) +} + +``` + + + + +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig + +init( + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + # highlight-start + supertokens_config=SupertokensConfig( + connection_uri='', + api_key='' + ), + # highlight-end + framework='...', # type: ignore + recipe_list=[ + #... + ] +) +``` + + + + + + + +## Customize Your Authentication Flow + +After you have connected the Backend SDK to a specific **SuperTokens Core Service**, you can go check the rest of the documentation. +There are several sections that show you how to customize the authentication experience to fit your specific needs. +Some of the most common subjects are: + +- [**Add Email Verification**](../common-customizations/email-verification/about) +- [**Add a Custom Redirect Action**](../pre-built-ui/auth-redirection) +- [**Use Custom Session Management**](../common-customizations/sessions/session-verification-in-api/get-session) +- [**Share Sessions Across Subdomains**](../common-customizations/sessions/share-sessions-across-sub-domains) +- [**Post Sign In Actions**](../common-customizations/handling-signinup-success) + + +## Explore Additional Features + +You can also review the additional features that **SuperTokens** exposes. +Those can help you extend the authentication implementation on different levels. + +- [**Self Host SuperTokens Core**](../pre-built-ui/setup/core/with-docker) +- [**Migration Guide**](../migration/about) +- [**Multi Factor Authentication**](../../mfa/introduction) +- [**Manage Users through the User Management Dashboard**](../pre-built-ui/setup/user-management-dashboard/setup) +- [**Multi Tenancy**](../../multitenancy/introduction) +- [**User Roles and Permissions**](../user-roles/initialisation) + diff --git a/v2/thirdparty/sidebars.js b/v2/thirdparty/sidebars.js index 5663b3762..f93b46c31 100644 --- a/v2/thirdparty/sidebars.js +++ b/v2/thirdparty/sidebars.js @@ -1,157 +1,35 @@ module.exports = { sidebar: [ { + label: "quickstart", type: "category", - label: "Start Here", customProps: { highlightGroup: true, }, collapsed: false, items: [ - "introduction", { - type: "category", - label: "Quick setup with Pre built UI", - customProps: { - categoryIcon: "lightning", - }, - items: [ - { - type: "category", - label: "Setup", - collapsed: false, - items: [ - "pre-built-ui/setup/frontend", - "pre-built-ui/setup/backend", - { - type: "category", - label: "Step 3: Core", - items: [ - { - type: "category", - label: "Self hosted", - items: [ - "pre-built-ui/setup/core/with-docker", - "pre-built-ui/setup/core/without-docker", - { - type: "category", - label: "Database Setup", - items: [ - "pre-built-ui/setup/database-setup/mysql", - "pre-built-ui/setup/database-setup/postgresql", - "pre-built-ui/setup/database-setup/rename-database-tables", - ], - }, - ], - }, - "pre-built-ui/setup/core/saas-setup", - ], - }, - { - type: "category", - label: "Step 4: User management dashboard", - items: [ - "pre-built-ui/setup/user-management-dashboard/setup", - "pre-built-ui/setup/user-management-dashboard/users-listing-and-details", - "pre-built-ui/setup/user-management-dashboard/managing-user-roles-and-permissions", - { - type: "category", - label: "Tenant management", - items: [ - "pre-built-ui/setup/user-management-dashboard/tenant-management/overview", - "pre-built-ui/setup/user-management-dashboard/tenant-management/details", - "pre-built-ui/setup/user-management-dashboard/tenant-management/third-party", - ], - }, - ], - }, - ], - }, - "pre-built-ui/handling-session-tokens", - "pre-built-ui/securing-routes", - "pre-built-ui/sign-out", - "pre-built-ui/auth-redirection", - "pre-built-ui/enable-email-verification", - "pre-built-ui/multitenant-login", - { - type: "category", - label: "Further Reading", - items: [ - "pre-built-ui/further-reading/thirdparty-login", - "pre-built-ui/further-reading/email-verification", - ], - }, - ], + id: "quickstart/introduction", + type: "doc", + label: "Introduction", }, { - type: "category", - label: "Using your own UI / Custom UI", - customProps: { - categoryIcon: "pencil", - }, - items: [ - { - type: "category", - label: "Initialisation", - collapsed: false, - items: [ - "custom-ui/init/frontend", - "custom-ui/init/backend", - { - type: "category", - label: "Step 3: Core", - items: [ - { - type: "category", - label: "Self hosted", - items: [ - "custom-ui/init/core/with-docker", - "custom-ui/init/core/without-docker", - { - type: "category", - label: "Database Setup", - items: [ - "custom-ui/init/database-setup/mysql", - "custom-ui/init/database-setup/postgresql", - "custom-ui/init/database-setup/rename-database-tables", - ], - }, - ], - }, - "custom-ui/init/core/saas-setup", - ], - }, - { - type: "category", - label: "Step 4: User management dashboard", - items: [ - "custom-ui/init/user-management-dashboard/setup", - "custom-ui/init/user-management-dashboard/users-listing-and-details", - "custom-ui/init/user-management-dashboard/managing-user-roles-and-permissions", - { - type: "category", - label: "Tenant management", - items: [ - "custom-ui/init/user-management-dashboard/tenant-management/overview", - "custom-ui/init/user-management-dashboard/tenant-management/details", - "custom-ui/init/user-management-dashboard/tenant-management/third-party", - ], - }, - ], - }, - ], - }, - "custom-ui/thirdparty-login", - "custom-ui/handling-session-tokens", - "custom-ui/securing-routes", - "custom-ui/sign-out", - "custom-ui/enable-email-verification", - "custom-ui/multitenant-login", - ], + id: "quickstart/frontend-setup", + type: "doc", + label: "Frontend Setup", + }, + { + id: "quickstart/backend-setup", + type: "doc", + label: "Backend Setup", + }, + { + id: "quickstart/next-steps", + type: "doc", + label: "Next Steps", }, ], }, - "user-object", { type: "category", label: "Integrations", @@ -371,6 +249,11 @@ module.exports = { "common-customizations/sessions/protecting-frontend-routes", "common-customizations/sessions/with-jwt/read-jwt", "common-customizations/sessions/ssr", + { + id: "pre-built-ui/handling-session-tokens", + type: "doc", + label: "Access Session Tokens", + }, { type: "category", label: "Reading / modifying session claims", @@ -379,6 +262,12 @@ module.exports = { "common-customizations/sessions/claims/claim-validators", ], }, + { id: "pre-built-ui/sign-out", type: "doc", label: "Add Sign Out" }, + { + id: "pre-built-ui/auth-redirection", + type: "doc", + label: "Add Redirect Actions", + }, "common-customizations/sessions/revoke-session", "common-customizations/sessions/anonymous-session", "common-customizations/sessions/user-impersonation", @@ -474,6 +363,7 @@ module.exports = { "common-customizations/embed-sign-in-up-form", ], }, + "add-multiple-clients-for-the-same-provider", "common-customizations/get-user-info", "common-customizations/user-pagination", "common-customizations/delete-user", @@ -552,6 +442,28 @@ module.exports = { }, "common-customizations/multiple-clients", "common-customizations/userid-format", + { + id: "pre-built-ui/setup/core/saas-setup", + label: "Connecting to the SuperTokens Core Managed Service", + type: "doc", + }, + { + type: "category", + label: "Self Hosting SuperTokens Core", + items: [ + "pre-built-ui/setup/core/with-docker", + "pre-built-ui/setup/core/without-docker", + { + type: "category", + label: "Database Setup", + items: [ + "pre-built-ui/setup/database-setup/mysql", + "pre-built-ui/setup/database-setup/postgresql", + "pre-built-ui/setup/database-setup/rename-database-tables", + ], + }, + ], + }, { type: "category", label: @@ -681,6 +593,24 @@ module.exports = { "mfa", "multi-tenant", "attack-protection-suite", + { + type: "category", + label: "User Management dashboard", + items: [ + "pre-built-ui/setup/user-management-dashboard/setup", + "pre-built-ui/setup/user-management-dashboard/users-listing-and-details", + "pre-built-ui/setup/user-management-dashboard/managing-user-roles-and-permissions", + { + type: "category", + label: "Tenant Management", + collapsed: true, + items: [ + "pre-built-ui/setup/user-management-dashboard/tenant-management/overview", + "pre-built-ui/setup/user-management-dashboard/tenant-management/details", + ], + }, + ], + }, ], }, "scalability", @@ -720,11 +650,20 @@ module.exports = { label: "References", items: [ "architecture", + "user-object", "other-frameworks", "appinfo", "sdks", "apis", "compatibility-table", + { + type: "category", + label: "Prebuilt UI Components", + items: [ + "pre-built-ui/further-reading/thirdparty-login", + "pre-built-ui/further-reading/email-verification", + ], + }, ], }, ], diff --git a/v2/thirdpartyemailpassword/add-multiple-clients-for-the-same-provider.mdx b/v2/thirdpartyemailpassword/add-multiple-clients-for-the-same-provider.mdx new file mode 100644 index 000000000..960735447 --- /dev/null +++ b/v2/thirdpartyemailpassword/add-multiple-clients-for-the-same-provider.mdx @@ -0,0 +1,209 @@ +--- +id: add-multiple-clients-for-the-same-provider +title: Add Multiple Clients for the Same Provider +hide_title: true +show_ui_switcher: false +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" +import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" +import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; +import AppInfoForm from "/src/components/appInfoForm" + +# Add Multiple Clients for the Same Provider + +If you have social/SSO login for your web and mobile app, then you might need to setup different Client ID/Secret for the same provider on the backend. +For example, in case of Apple login, Apple gives you different client IDs for iOS login vs web & Android login (same client ID for web and Android). + +In order to get this to work, you would need to add additional clients to the Apple.init on the backend. Each client would need to be uniquely identified and this is done using the `clientType` string. For example, you can add one `clientType` for `web-and-android` and one for `ios`. + + + + +```tsx +import { ProviderInput } from "supertokens-node/recipe/thirdparty/types"; + +let providers: ProviderInput[] = [ + { + config: { + thirdPartyId: "apple", + clients: [{ + clientType: "web-and-android", + clientId: "...", + additionalConfig: { + "keyId": "...", + "privateKey": "...", + "teamId": "...", + } + }, { + clientType: "ios", + clientId: "...", + additionalConfig: { + "keyId": "...", + "privateKey": "...", + "teamId": "...", + } + }] + } + } +] +``` + + + + +```go +import ( + "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" +) + +func main() { + _ = []tpmodels.ProviderInput{{ + Config: tpmodels.ProviderConfig{ + ThirdPartyId: "apple", + Clients: []tpmodels.ProviderClientConfig{ + { + ClientType: "web-and-android", + ClientID: "...", + AdditionalConfig: map[string]interface{}{ + "keyId": "...", + "privateKey": "...", + "teamId": "...", + }, + }, + { + ClientType: "ios", + ClientID: "...", + AdditionalConfig: map[string]interface{}{ + "keyId": "...", + "privateKey": "...", + "teamId": "...", + }, + }, + }, + }, + }} +} +``` + + + + +```python +from supertokens_python.recipe.thirdparty.provider import ProviderInput, ProviderConfig, ProviderClientConfig + +providers = [ + ProviderInput( + config=ProviderConfig( + third_party_id="apple", + clients=[ + ProviderClientConfig( + client_type="web-and-android", + client_id="...", + additional_config={ + "keyId": "...", + "privateKey": "...", + "teamId": "...", + }, + ), + ProviderClientConfig( + client_type="ios", + client_id="...", + additional_config={ + "keyId": "...", + "privateKey": "...", + "teamId": "...", + }, + ), + ], + ), + ), +] +``` + + + + +For the frontend, you would need to use the right `clientType` as shown below: + + + + + + + + + +We pass in the `clientType` during the init call. + +```tsx +import SuperTokens from 'supertokens-web-js'; + +SuperTokens.init({ + appInfo: { + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}", + appName: "...", + }, + // highlight-next-line + clientType: "web-and-android", + recipeList: [/*...*/], +}); +``` + +If you are using our pre built UI SDK (supertokens-auth-react) as well, you can provide the `clientType` config to it as follows: + +```tsx +import SuperTokens from 'supertokens-auth-react'; + +SuperTokens.init({ + appInfo: { + apiDomain: "^{form_apiDomain}", + websiteDomain: "^{form_websiteDomain}", + apiBasePath: "^{form_apiBasePath}", + appName: "...", + }, + // highlight-next-line + clientType: "web-and-android", + recipeList: [/*...*/], +}); +``` + + + + + + + + +We pass in the `clientType` during the init call. + +```tsx +import supertokens from "supertokens-web-js-script"; +supertokens.init({ + appInfo: { + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}", + appName: "...", + }, + // highlight-next-line + clientType: "web-and-android", + recipeList: [/*...*/], +}); +``` + + + + + + + + + + +When making calls to the APIs from your mobile app, the request body also takes a `clientType` prop as seen in the above API calls. + + + diff --git a/v2/thirdpartyemailpassword/common-customizations/multi-tenancy/new-tenant-config.mdx b/v2/thirdpartyemailpassword/common-customizations/multi-tenancy/new-tenant-config.mdx index 02b501981..0dc90aa42 100644 --- a/v2/thirdpartyemailpassword/common-customizations/multi-tenancy/new-tenant-config.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/multi-tenancy/new-tenant-config.mdx @@ -25,168 +25,6 @@ The configuration mapped to each tenant contains information about which login m - - - - - -```tsx -import Multitenancy from "supertokens-node/recipe/multitenancy"; - -async function createNewTenant() { - - // highlight-start - let resp = await Multitenancy.createOrUpdateTenant("customer1", { - firstFactors: ["emailpassword", "thirdparty"] - }); - // highlight-end - - if (resp.createdNew) { - // new tenant was created - } else { - // existing tenant's config was modified. - } -} -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/multitenancy" - "github.com/supertokens/supertokens-golang/recipe/multitenancy/multitenancymodels" -) - -func main() { - tenantId := "customer1" - thirdPartyEnabled := true - emailPasswordEnabled := true - - // highlight-start - resp, err := multitenancy.CreateOrUpdateTenant(tenantId, multitenancymodels.TenantConfig{ - ThirdPartyEnabled: &thirdPartyEnabled, - EmailPasswordEnabled: &emailPasswordEnabled, - }) - // highlight-end - - if err != nil { - // handle error - } - if resp.OK.CreatedNew { - // new tenant was created - } else { - // existing tenant's config was modified. - } -} -``` - - - - - - - -```python -from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate - - -async def update_tenant(): - result = await create_or_update_tenant( - "customer1", - config=TenantConfigCreateOrUpdate( - first_factors=["thirdparty", "emailpassword"], - ), - ) - - if result.status != "OK": - print("Error creating or updating tenant") - elif result.created_new: - print("New tenant was created") - else: - print("Existing tenant's config was modified") -``` - - - - - -```python -from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate - - -def update_tenant(): - result = create_or_update_tenant( - "customer1", - config=TenantConfigCreateOrUpdate( - first_factors=["thirdparty", "emailpassword"], - ), - ) - - if result.status != "OK": - print("Error creating or updating tenant") - elif result.created_new: - print("New tenant was created") - else: - print("Existing tenant's config was modified") -``` - - - - - - - - - - - - - - -```bash -curl --location --request PUT '^{coreInjector_uri_without_quotes}/recipe/multitenancy/tenant/v2' \ ---header 'api-key: ^{coreInjector_api_key_without_quotes}' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "tenantId": "customer1", - "firstFactors": ["emailpassword", "thirdparty"] -}' -``` - - - - - -```bash -curl --location --request PUT '^{coreInjector_uri_without_quotes}/recipe/multitenancy/tenant' \ ---header 'api-key: ^{coreInjector_api_key_without_quotes}' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "tenantId": "customer1", - "emailPasswordEnabled": true, - "thirdPartyEnabled": true -}' -``` - - - - - - - - - - - -Create Tenant - - - - - ## Step 2: Configure the third party providers for the tenant @@ -198,174 +36,6 @@ Once again, you can add / modify this config dynamically using our backend SDK o - - - - - -```tsx -import Multiteancy from "supertokens-node/recipe/multitenancy"; - -async function createTenant() { - let resp = await Multiteancy.createOrUpdateThirdPartyConfig("customer1", { - thirdPartyId: "active-directory", - name: "Active Directory", - clients: [{ - clientId: "...", - clientSecret: "...", - }], - oidcDiscoveryEndpoint: "https://login.microsoftonline.com//v2.0/.well-known/openid-configuration", - }); - - if (resp.createdNew) { - // Active Directory added to customer1 - } else { - // Existing active directory config overwritten for customer1 - } -} -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/multitenancy" - "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" -) - -func main() { - tenantId := "customer1" - - // highlight-start - resp, err := multitenancy.CreateOrUpdateThirdPartyConfig(tenantId, tpmodels.ProviderConfig{ - ThirdPartyId: "active-directory", - Name: "Active Directory", - Clients: []tpmodels.ProviderClientConfig{ - { - ClientID: "...", - ClientSecret: "...", - }, - }, - OIDCDiscoveryEndpoint: "https://login.microsoftonline.com//v2.0/.well-known/openid-configuration", - }, nil, nil) - // highlight-end - - if err != nil { - // handle error - } - if resp.OK.CreatedNew { - // Active Directory added to customer1 - } else { - // Existing active directory config overwritten for customer1 - } -} -``` - - - - - - - -```python -from supertokens_python.recipe.multitenancy.asyncio import create_or_update_third_party_config -from supertokens_python.recipe.thirdparty.provider import ProviderConfig, ProviderClientConfig - -async def update_tenant(): - result = await create_or_update_third_party_config( - "customer1", - config=ProviderConfig( - third_party_id="active-directory", - name="Active Directory", - clients=[ - ProviderClientConfig( - client_id="...", - client_secret="...", - ) - ], - oidc_discovery_endpoint="https://login.microsoftonline.com//v2.0/.well-known/openid-configuration", - ), - ) - - if result.status != "OK": - print("Error adding active directory to tenant") - elif result.created_new: - print("Active directory was added to the tenant") - else: - print("Existing tenant's active directory config was modified") -``` - - - - - -```python -from supertokens_python.recipe.multitenancy.syncio import create_or_update_third_party_config -from supertokens_python.recipe.thirdparty.provider import ProviderConfig, ProviderClientConfig - -result = create_or_update_third_party_config( - "customer1", - config=ProviderConfig( - third_party_id="active-directory", - name="Active Directory", - clients=[ - ProviderClientConfig( - client_id="...", - client_secret="...", - ) - ] - ), - ) - -if result.status != "OK": - print("Error creating or updating tenant") -elif result.created_new: - print("New tenant was created") -else: - print("Existing tenant's config was modified") -``` - - - - - - - - - - -```bash -curl --location --request PUT '^{coreInjector_uri_without_quotes}//recipe/multitenancy/config/thirdparty' \ ---header 'api-key: ^{coreInjector_api_key_without_quotes}' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "config": { - "thirdPartyId": "active-directory", - "name": "Active Directory", - "clients": [ - { - "clientId": "...", - "clientSecret": "..." - } - ], - "oidcDiscoveryEndpoint": "https://login.microsoftonline.com//v2.0/.well-known/openid-configuration" - } -}' -``` - - - - - - - -Create Third Party Provider - - - - - - The above code snippet shows how you can add an Active directory login to your tenant. The `clientId`, `clientSecret` and `directoryId` will be provided to you by your tenant. diff --git a/v2/thirdpartyemailpassword/common-customizations/multi-tenancy/overview.mdx b/v2/thirdpartyemailpassword/common-customizations/multi-tenancy/overview.mdx index 20e0ba5f4..2eb8a7ead 100644 --- a/v2/thirdpartyemailpassword/common-customizations/multi-tenancy/overview.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/multi-tenancy/overview.mdx @@ -11,22 +11,6 @@ import TabItem from '@theme/TabItem'; - - - -# Multitenant and Enterprise SSO login - -Multitenant login is a feature that lets you customize the login experience for each of your customers. For example, a customer `customer1` hosted on `customer1.yourdomain.com` can have login with `Active Directory` and `Google`, and another customer `customer2` hosted on `customer2.yourdomain.com` can have login with `Okta`, `Facebook` and email password based login. - -This is also the page that you should see if you want to implement sign in with: -- Okta (`thirdPartyId: "okta"`) -- SAML (`thirdPartyId: "boxy-saml"`) -- Active Directory (`thirdPartyId: "active-directory"`) -- Google Workspaces (`thirdPartyId: "google-workspaces"`) -- GitLab (`thirdPartyId: "gitlab"`) -- Bitbucket (`thirdPartyId: "bitbucket"`) -- Or any other workforce IdP - diff --git a/v2/thirdpartyemailpassword/common-customizations/sessions/protecting-frontend-routes.mdx b/v2/thirdpartyemailpassword/common-customizations/sessions/protecting-frontend-routes.mdx index 9e5719247..7a0f1c394 100644 --- a/v2/thirdpartyemailpassword/common-customizations/sessions/protecting-frontend-routes.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/sessions/protecting-frontend-routes.mdx @@ -291,133 +291,6 @@ async function shouldLoadRoute(): Promise { - - - - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```tsx -import Session from 'supertokens-web-js/recipe/session'; - -async function doesSessionExist() { - if (await Session.doesSessionExist()) { - // user is logged in - } else { - // user has not logged in yet - } -} -``` - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```tsx -import Session from 'supertokens-web-js-script/recipe/session'; -async function doesSessionExist() { - if (await Session.doesSessionExist()) { - // user is logged in - } else { - // user has not logged in yet - } -} -``` - - - - - - - - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```tsx -import SuperTokens from 'supertokens-react-native'; - -async function doesSessionExist() { - if (await SuperTokens.doesSessionExist()) { - // user is logged in - } else { - // user has not logged in yet - } -} -``` - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```kotlin -import android.app.Application -import com.supertokens.session.SuperTokens - -class MainApplication: Application() { - fun doesSessionExist() { - if (SuperTokens.doesSessionExist(this.applicationContext)) { - // user is logged in - } else { - // user has not logged in yet - } - } -} -``` - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```swift -import UIKit -import SuperTokensIOS - -fileprivate class ViewController: UIViewController { - func doesSessionExist() { - if SuperTokens.doesSessionExist() { - // User is logged in - } else { - // User is not logged in - } - } -} -``` - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```dart -import 'package:supertokens_flutter/supertokens.dart'; - -Future doesSessionExist() async { - return await SuperTokens.doesSessionExist(); -} -``` - - - - - - - - - ## Verifying the claims of a session diff --git a/v2/thirdpartyemailpassword/custom-ui/email-password-login.mdx b/v2/thirdpartyemailpassword/custom-ui/email-password-login.mdx index 24e358886..c9a0795fc 100644 --- a/v2/thirdpartyemailpassword/custom-ui/email-password-login.mdx +++ b/v2/thirdpartyemailpassword/custom-ui/email-password-login.mdx @@ -4,428 +4,9 @@ title: Email Password login hide_title: true --- - - +import Redirector from '/src/components/Redirector'; - - - -import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" -import TabItem from '@theme/TabItem'; -import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" -import AppInfoForm from "/src/components/appInfoForm" + -# Email Password login - -## Sign up form - - - - -Call the following function when the user clicks on the sign up button. - - - - -```tsx -import { signUp } from "supertokens-web-js/recipe/emailpassword"; - -async function signUpClicked(email: string, password: string) { - try { - let response = await signUp({ - formFields: [{ - id: "email", - value: email - }, { - id: "password", - value: password - }] - }) - - if (response.status === "FIELD_ERROR") { - // one of the input formFields failed validation - response.formFields.forEach(formField => { - if (formField.id === "email") { - // Email validation failed (for example incorrect email syntax), - // or the email is not unique. - window.alert(formField.error) - } else if (formField.id === "password") { - // Password validation failed. - // Maybe it didn't match the password strength - window.alert(formField.error) - } - }) - } else if (response.status === "SIGN_UP_NOT_ALLOWED") { - // the reason string is a user friendly message - // about what went wrong. It can also contain a support code which users - // can tell you so you know why their sign up was not allowed. - window.alert(response.reason) - } else { - // sign up successful. The session tokens are automatically handled by - // the frontend SDK. - window.location.href = "/homepage" - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - -```tsx -import supertokensEmailPassword from "supertokens-web-js-script/recipe/emailpassword"; -async function signUpClicked(email: string, password: string) { - try { - let response = await supertokensEmailPassword.signUp({ - formFields: [{ - id: "email", - value: email - }, { - id: "password", - value: password - }] - }) - - if (response.status === "FIELD_ERROR") { - // one of the input formFields failed validation - response.formFields.forEach(formField => { - if (formField.id === "email") { - // Email validation failed (for example incorrect email syntax), - // or the email is not unique. - window.alert(formField.error) - } else if (formField.id === "password") { - // Password validation failed. - // Maybe it didn't match the password strength - window.alert(formField.error) - } - }) - } else if (response.status === "SIGN_UP_NOT_ALLOWED") { - // the reason string is a user friendly message - // about what went wrong. It can also contain a support code which users - // can tell you so you know why their sign in was not allowed. - window.alert(response.reason) - } else { - // sign up successful. The session tokens are automatically handled by - // the frontend SDK. - window.location.href = "/homepage" - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - - - - - - - -Call the follwing API when the user clicks on the sign up button (the command below can be tried on your terminal). - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signup' \ ---header 'Content-Type: application/json; charset=utf-8' \ ---data-raw '{ - "formFields": [{ - "id": "email", - "value": "john@example.com" - }, { - "id": "password", - "value": "somePassword123" - }] -}' -``` - - - -The response body from the API call has a `status` property in it: -- `status: "OK"`: User creation was successful. The response also contains more information about the user, for example their user ID. -- `status: "FIELD_ERROR"`: One of the form field inputs failed validation. The response body contains information about which form field input based on the `id`: - - The email could fail validation if it's syntactically not an email, of it it's not unique. - - The password could fail validation if it's not string enough (as defined by the backend password validator). - - Either way, you want to show the user an error next to the input form field. -- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. -- `status: "SIGN_UP_NOT_ALLOWED"`: This can happen during automatic account linking or during MFA. The `reason` prop that's in the response body contains a support code using which you can see why the sign up was not allowed. - - - - - -The `formFields` input is a key-value array. You must provide it an `email` and a `password` value at a minimum. If you want to provide additional items, for example the user's name or age, you can append it to the array like so: - -```json -{ - "formFields": [{ - "id": "email", - "value": "john@example.com" - }, { - "id": "password", - "value": "somePassword123" - }, { - "id": "name", - "value": "John Doe" - }] -} -``` - -On the backend, the `formFields` array will be available to you for consumption. - -:::note -On success, the backend will send back session tokens as part of the response headers which will be automatically handled by our frontend SDK for you. -::: - -### Checking if email is unique - -As a part of the sign up form, you may want to explicitly check that the entered email is unique. Whilst this is already done via the sign up API call, it may be a better UX to warn the user about a non unique email right after they finish typing it. - - - - - - - -```tsx -import { doesEmailExist } from "supertokens-web-js/recipe/emailpassword"; - -async function checkEmail(email: string) { - try { - let response = await doesEmailExist({ - email - }); - - if (response.doesExist) { - window.alert("Email already exists. Please sign in instead") - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - -```tsx -import supertokensEmailPassword from "supertokens-web-js-script/recipe/emailpassword"; -async function checkEmail(email: string) { - try { - let response = await supertokensEmailPassword.doesEmailExist({ - email - }); - - if (response.doesExist) { - window.alert("Email already exists. Please sign in instead") - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - - - - - - - -```bash -curl --location --request GET '^{form_apiDomain}^{form_apiBasePath}/emailpassword/email/exists?email=john@example.com' -``` - - - -The response body from the API call has a `status` property in it: -- `status: "OK"`: The response will also contain a `exists` boolean which will be `true` if the input email already belongs to an email password user. -- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. - - - - - -## Sign in form - - - - -Call the follwing function when the user clicks on the sign in button. - - - - -```tsx -import { signIn } from "supertokens-web-js/recipe/emailpassword"; - -async function signInClicked(email: string, password: string) { - try { - let response = await signIn({ - formFields: [{ - id: "email", - value: email - }, { - id: "password", - value: password - }] - }) - - if (response.status === "FIELD_ERROR") { - response.formFields.forEach(formField => { - if (formField.id === "email") { - // Email validation failed (for example incorrect email syntax). - window.alert(formField.error) - } - }) - } else if (response.status === "WRONG_CREDENTIALS_ERROR") { - window.alert("Email password combination is incorrect.") - } else if (response.status === "SIGN_IN_NOT_ALLOWED") { - // the reason string is a user friendly message - // about what went wrong. It can also contain a support code which users - // can tell you so you know why their sign in was not allowed. - window.alert(response.reason) - } else { - // sign in successful. The session tokens are automatically handled by - // the frontend SDK. - window.location.href = "/homepage" - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - -```tsx -import supertokensEmailPassword from "supertokens-web-js-script/recipe/emailpassword"; -async function signInClicked(email: string, password: string) { - try { - let response = await supertokensEmailPassword.signIn({ - formFields: [{ - id: "email", - value: email - }, { - id: "password", - value: password - }] - }) - - if (response.status === "FIELD_ERROR") { - // one of the input formFields failed validation - response.formFields.forEach(formField => { - if (formField.id === "email") { - // Email validation failed (for example incorrect email syntax). - window.alert(formField.error) - } - }) - } else if (response.status === "WRONG_CREDENTIALS_ERROR") { - window.alert("Email password combination is incorrect.") - } else if (response.status === "SIGN_IN_NOT_ALLOWED") { - // the reason string is a user friendly message - // about what went wrong. It can also contain a support code which users - // can tell you so you know why their sign in was not allowed. - window.alert(response.reason) - } else { - // sign in successful. The session tokens are automatically handled by - // the frontend SDK. - window.location.href = "/homepage" - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - - - - - - - -Call the follwing API when the user clicks on the sign in button (the command below can be tried on your terminal). - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signin' \ ---header 'Content-Type: application/json; charset=utf-8' \ ---data-raw '{ - "formFields": [{ - "id": "email", - "value": "john@example.com" - }, { - "id": "password", - "value": "somePassword123" - }] -}' -``` - - - -The response body from the API call has a `status` property in it: -- `status: "OK"`: User sign in was successful. The response also contains more information about the user, for example their user ID. -- `status: "WRONG_CREDENTIALS_ERROR"`: The input email and password combination is incorrect. -- `status: "FIELD_ERROR"`: This indicates that the input email did not pass the backend validation - probably because it's syntactically not an email. You want to show the user an error next to the email input form field. -- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. -- `status: "SIGN_IN_NOT_ALLOWED"`: This can happen during automatic account linking or during MFA. The `reason` prop that's in the response body contains a support code using which you can see why the sign in was not allowed. - - - - - -:::important -On success, the backend will send back session tokens as part of the response headers which will be automatically handled by our frontend SDK for you. -::: - - - -## See also - -- [Post sign in / sign up action](../common-customizations/handling-signinup-success) -- [Adding extra form fields in sign up](../common-customizations/signup-form/adding-fields) -- [Changing fields validation logic on the backend](../common-customizations/signup-form/field-validators) -- [Password hashing algorithms](../common-customizations/password-hashing/about) -- [Disabling sign up on the backend](../advanced-customizations/apis-override/disabling) -- [Customising user ID format](../common-customizations/userid-format) diff --git a/v2/thirdpartyemailpassword/custom-ui/enable-email-verification.mdx b/v2/thirdpartyemailpassword/custom-ui/enable-email-verification.mdx index b56cdf984..4f3ade3f4 100644 --- a/v2/thirdpartyemailpassword/custom-ui/enable-email-verification.mdx +++ b/v2/thirdpartyemailpassword/custom-ui/enable-email-verification.mdx @@ -4,1238 +4,8 @@ title: Enable email verification hide_title: true --- -# Enable email verification +import Redirector from '/src/components/Redirector'; -:::important -For social / third party logins, the user's email is automatically marked as verified if the user had verified their email to the login provider. -::: - - - + -import CustomAdmonition from "/src/components/customAdmonition" -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"; - - -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 - - - - -```tsx -import SuperTokens from "supertokens-node"; -import EmailVerification from "supertokens-node/recipe/emailverification"; -import Session from "supertokens-node/recipe/session"; - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // highlight-start - EmailVerification.init({ - mode: "REQUIRED", // or "OPTIONAL" - }), - // highlight-end - Session.init(), - ], -}); -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/emailverification" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evmodels" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - supertokens.Init(supertokens.TypeInput{ - RecipeList: []supertokens.Recipe{ - // highlight-start - emailverification.Init(evmodels.TypeInput{ - Mode: evmodels.ModeRequired, // or evmodels.ModeOptional - }), - // highlight-end - session.Init(&sessmodels.TypeInput{}), - }, - }) -} -``` - - - - -```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import session -from supertokens_python.recipe import emailverification - -init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore - recipe_list=[ - # highlight-start - emailverification.init(mode='REQUIRED'), # or 'OPTIONAL' - # highlight-end - session.init() - ] -) -``` - - - - - - - - - - -import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" -import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" -import WebJsInjector from "/src/components/webJsInjector" - -## Step 2: Frontend setup - - - - - - - -```tsx -import SuperTokens from "supertokens-web-js"; -import EmailVerification from "supertokens-web-js/recipe/emailverification"; -import Session from "supertokens-web-js/recipe/session"; - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - }, - recipeList: [ - // highlight-start - EmailVerification.init(), - Session.init(), - ], -}); -``` - - - - -Add the following ` -``` - - - -Then call the `supertokensEmailVerification.init` function as shown below - -```tsx -import supertokens from "supertokens-web-js-script"; -import supertokensEmailVerification from "supertokens-web-js-script/recipe/emailverification"; -import supertokensSession from "supertokens-web-js-script/recipe/session"; -supertokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - }, - recipeList: [ - // highlight-start - supertokensEmailVerification.init(), - supertokensSession.init(), - ], -}); -``` - - - - - - - - -:::success -No specific action required here. -::: - - - - - - - - - - -## Step 3: Checking if the user's email is verified in your APIs - -

      If using REQUIRED mode

      - -On the backend, when you initialize the email verification recipe in this mode, the `verifySession` middleware automatically checks if the user's email is verified based on the contents of the session's payload. If the email is not verified, the `verifySession` middleware will return a `403` status code to the client. - -

      If using OPTIONAL mode

      - -In this mode, you need to check if the email is verified yourself in the APIs in which you want this constraint. The verification status should already be in the session's payload. - - - - - - -```tsx -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import express from "express"; -import { SessionRequest } from "supertokens-node/framework/express"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let app = express(); - -app.post( - "/update-blog", - verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - }), - async (req: SessionRequest, res) => { - // All validator checks have passed and the user has a verified email address - } -); -``` - - - -```tsx -import Hapi from "@hapi/hapi"; -import { verifySession } from "supertokens-node/recipe/session/framework/hapi"; -import {SessionRequest} from "supertokens-node/framework/hapi"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let server = Hapi.server({ port: 8000 }); - -server.route({ - path: "/update-blog", - method: "post", - options: { - pre: [ - { - method: verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - }), - }, - ], - }, - handler: async (req: SessionRequest, res) => { - // All validator checks have passed and the user has a verified email address - } -}) -``` - - - -```tsx -import Fastify from "fastify"; -import { verifySession } from "supertokens-node/recipe/session/framework/fastify"; -import { SessionRequest } from "supertokens-node/framework/fastify"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let fastify = Fastify(); - -fastify.post("/update-blog", { - preHandler: verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - }), -}, async (req: SessionRequest, res) => { - // All validator checks have passed and the user has a verified email address -}); -``` - - - - -```tsx -import { verifySession } from "supertokens-node/recipe/session/framework/awsLambda"; -import { SessionEvent } from "supertokens-node/framework/awsLambda"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -async function updateBlog(awsEvent: SessionEvent) { - // All validator checks have passed and the user has a verified email address -}; - -exports.handler = verifySession(updateBlog, { - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] -}); -``` - - - - -```tsx -import KoaRouter from "koa-router"; -import { verifySession } from "supertokens-node/recipe/session/framework/koa"; -import {SessionContext} from "supertokens-node/framework/koa"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let router = new KoaRouter(); - -router.post("/update-blog", verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - }), async (ctx: SessionContext, next) => { - // All validator checks have passed and the user has a verified email address -}); -``` - - - - -```tsx -import { inject, intercept } from "@loopback/core"; -import { RestBindings, MiddlewareContext, post, response } from "@loopback/rest"; -import { verifySession } from "supertokens-node/recipe/session/framework/loopback"; -import Session from "supertokens-node/recipe/session"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -class SetRole { - constructor(@inject(RestBindings.Http.CONTEXT) private ctx: MiddlewareContext) { } - @post("/update-blog") - @intercept(verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - })) - @response(200) - async handler() { - // All validator checks have passed and the user has a verified email address - } -} -``` - - - - -```tsx -import { superTokensNextWrapper } from 'supertokens-node/nextjs' -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import { SessionRequest } from "supertokens-node/framework/express"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -// highlight-start -export default async function setRole(req: SessionRequest, res: any) { - await superTokensNextWrapper( - async (next) => { - await verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - })(req, res, next); - }, - req, - res - ) - // All validator checks have passed and the user has a verified email address -} -``` - - - - -```tsx -import SuperTokens from "supertokens-node"; -import { NextResponse, NextRequest } from "next/server"; -import { withSession } from "supertokens-node/nextjs"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; -// @ts-ignore -import { backendConfig } from "@/app/config/backend"; - -SuperTokens.init(backendConfig()); - -export async function POST(request: NextRequest) { - return withSession(request, async (err, session) => { - if (err) { - return NextResponse.json(err, { status: 500 }); - } - // All validator checks have passed and the user has a verified email address - return NextResponse.json({ message: "Your email is verified!" }); - }, - { - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - } - ); -} -``` - - - - -```tsx -import { Controller, Post, UseGuards, Request, Response, Session } from "@nestjs/common"; -import { SessionContainer, SessionClaimValidator } from "supertokens-node/recipe/session"; -// @ts-ignore -import { AuthGuard } from './auth/auth.guard'; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -@Controller() -export class ExampleController { - @Post('example') - @UseGuards(new AuthGuard({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators: SessionClaimValidator[]) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - })) - async postExample(@Session() session: SessionContainer): Promise { - // All validator checks have passed and the user has a verified email address - return true; - } -} -``` - - - - - - - - -```go -import ( - "net/http" - - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - _ = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - session.VerifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }, exampleAPI).ServeHTTP(rw, r) - }) -} - -func exampleAPI(w http.ResponseWriter, r *http.Request) { - // TODO: session is verified and all validators have passed.. -} -``` - - - - -```go -import ( - "net/http" - - "github.com/gin-gonic/gin" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - router := gin.New() - - // Wrap the API handler in session.VerifySession - router.POST("/likecomment", verifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }), exampleAPI) -} - -// This is a function that wraps the supertokens verification function -// to work the gin -func verifySession(options *sessmodels.VerifySessionOptions) gin.HandlerFunc { - return func(c *gin.Context) { - session.VerifySession(options, func(rw http.ResponseWriter, r *http.Request) { - c.Request = c.Request.WithContext(r.Context()) - c.Next() - })(c.Writer, c.Request) - // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly - c.Abort() - } -} - -func exampleAPI(c *gin.Context) { - // TODO: session is verified and all claim validators pass. -} -``` - - - - -```go -import ( - "net/http" - - "github.com/go-chi/chi" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - r := chi.NewRouter() - - // Wrap the API handler in session.VerifySession - r.Post("/likecomment", session.VerifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }, exampleAPI)) -} - -func exampleAPI(w http.ResponseWriter, r *http.Request) { - // TODO: session is verified and all claim validators pass. -} -``` - - - - -```go -import ( - "net/http" - - "github.com/gorilla/mux" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - router := mux.NewRouter() - - // Wrap the API handler in session.VerifySession - router.HandleFunc("/likecomment", session.VerifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }, exampleAPI)).Methods(http.MethodPost) -} - -func exampleAPI(w http.ResponseWriter, r *http.Request) { - // TODO: session is verified and all claim validators pass. -} -``` - - - - - - - - - -```python -from supertokens_python.recipe.session.framework.fastapi import verify_session -from supertokens_python.recipe.emailverification import EmailVerificationClaim -from supertokens_python.recipe.session import SessionContainer -from fastapi import Depends - -@app.post('/like_comment') # type: ignore -async def like_comment(session: SessionContainer = Depends( - verify_session( - # highlight-start - # We add the EmailVerificationClaim's is_verified validator - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \ - [EmailVerificationClaim.validators.is_verified()] - # highlight-end - ) -)): - # All validator checks have passed and the user has a verified email address - pass -``` - - - - -```python -from supertokens_python.recipe.session.framework.flask import verify_session -from supertokens_python.recipe.emailverification import EmailVerificationClaim - -@app.route('/update-jwt', methods=['POST']) # type: ignore -@verify_session( - # highlight-start - # We add the EmailVerificationClaim's is_verified validator - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \ - [EmailVerificationClaim.validators.is_verified()] - # highlight-end -) -def like_comment(): - # All validator checks have passed and the user has a verified email address - pass -``` - - - - -```python -from supertokens_python.recipe.session.framework.django.asyncio import verify_session -from django.http import HttpRequest -from supertokens_python.recipe.emailverification import EmailVerificationClaim - -@verify_session( - # highlight-start - # We add the EmailVerificationClaim's is_verified validator - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \ - [EmailVerificationClaim.validators.is_verified()] - # highlight-end -) -async def like_comment(request: HttpRequest): - # All validator checks have passed and the user has a verified email address - pass -``` - - - - - - - -We add the SDK's `EmailVerificationClaim` validator to the `verifySession` middleware call as shown above, and that will only allow access if the email is verified, else it will return `403` to the frontend. - - - - - - - -import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" - -## Step 4: Protecting frontend routes - - - - - - - -```tsx -import Session from "supertokens-web-js/recipe/session"; -import { EmailVerificationClaim } from "supertokens-web-js/recipe/emailverification"; - -async function shouldLoadRoute(): Promise { - if (await Session.doesSessionExist()) { - // highlight-start - let validationErrors = await Session.validateClaims(); - - if (validationErrors.length === 0) { - // user has verified their email address - return true; - } else { - for (const err of validationErrors) { - if (err.id === EmailVerificationClaim.id) { - // email is not verified - } - } - } - // highlight-end - } - // a session does not exist, or email is not verified - return false -} -``` - - - - -```tsx -import supertokensSession from "supertokens-web-js-script/recipe/session"; -import supertokensEmailVerification from "supertokens-web-js-script/recipe/emailverification"; -async function shouldLoadRoute(): Promise { - if (await supertokensSession.doesSessionExist()) { - // highlight-start - let validationErrors = await supertokensSession.validateClaims(); - - if (validationErrors.length === 0) { - // user has verified their email address - return true; - } else { - for (const err of validationErrors) { - if (err.id === supertokensEmailVerification.EmailVerificationClaim.id) { - // email is not verified - } - } - } - // highlight-end - } - // a session does not exist, or email is not verified - return false -} -``` - - - - -In your protected routes, you need to first check if a session exists, and then call the `Session.validateClaims` function as shown above. This function inspects the session's contents and runs claim validators on them. If a claim validator fails, it will be reflected in the `validationErrors` variable. The `EmailVerificationClaim` validator will be automatically checked by this function since you have initialized the email verification recipe. - -
      Handling 403 responses on the frontend - - - -If your frontend queries a protected API on your backend and it fails with a 403, you can call the `validateClaims` function and loop through the errors to know which claim has failed: - -```tsx -import axios from "axios"; -import Session from "supertokens-web-js/recipe/session"; -import { EmailVerificationClaim, sendVerificationEmail } from "supertokens-web-js/recipe/emailverification"; - -async function callProtectedRoute() { - try { - let response = await axios.get("^{form_apiDomain}/protectedroute"); - } catch (error) { - // highlight-start - if (axios.isAxiosError(error) && error.response?.status === 403) { - let validationErrors = await Session.validateClaims(); - for (let err of validationErrors) { - if (err.id === EmailVerificationClaim.id) { - // email verification claim check failed - // We call the sendEmail function defined in the next section to send the verification email. - // await sendEmail(); - } else { - // some other claim check failed (from the global validators list) - } - } - // highlight-end - - } - } -} -``` - - - -
      - -
      - - - - - - - -```tsx -import SuperTokens from 'supertokens-react-native'; - -async function checkIfEmailIsVerified() { - if (await SuperTokens.doesSessionExist()) { - - // highlight-start - let isVerified: boolean = (await SuperTokens.getAccessTokenPayloadSecurely())["st-ev"].v; - - if (isVerified) { - // TODO.. - } else { - // TODO.. - } - // highlight-end - } -} -``` - - - - - -```kotlin -import android.app.Application -import com.supertokens.session.SuperTokens -import org.json.JSONObject - -class MainApplication: Application() { - fun checkIfEmailIsVerified() { - val accessTokenPayload: JSONObject = SuperTokens.getAccessTokenPayloadSecurely(this); - val isVerified: Boolean = (accessTokenPayload.get("st-ev") as JSONObject).get("v") as Boolean - if (isVerified) { - // TODO.. - } else { - // TODO.. - } - } -} -``` - - - - - -```swift -import UIKit -import SuperTokensIOS - -fileprivate class ViewController: UIViewController { - func checkIfEmailIsVerified() { - if let accessTokenPayload: [String: Any] = try? SuperTokens.getAccessTokenPayloadSecurely(), let emailVerificationObject: [String: Any] = accessTokenPayload["st-ev"] as? [String: Any], let isVerified: Bool = emailVerificationObject["v"] as? Bool { - if isVerified { - // Email is verified - } else { - // Email is not verified - } - } - } -} -``` - - - - - -```dart -import 'package:supertokens_flutter/supertokens.dart'; - -Future checkIfEmailIsVerified() async { - var accessTokenPayload = await SuperTokens.getAccessTokenPayloadSecurely(); - - if (accessTokenPayload.containsKey("st-ev")) { - Map emailVerificationObject = accessTokenPayload["st-ev"]; - - if (emailVerificationObject.containsKey("v")) { - bool isVerified = emailVerificationObject["v"]; - - if (isVerified) { - // Email is verified - } else { - // Email is not verified - } - } - } -} -``` - - - - - -
      Handling 403 responses on the frontend - -If your frontend queries a protected API on your backend and it fails with a 403, you can check the value of the `st-ev` claim in the access token payload. If it is set to false you can send the verification email - -
      - -
      -
      - -import AppInfoForm from "/src/components/appInfoForm" - -## Step 5: Sending the email verification email - -When the email verification validators fail, or post sign up, you want to redirect the user to a screen telling them that a verification email has been sent to them. On this screen, you should call the following API - - - - - - - -```tsx -import { sendVerificationEmail } from "supertokens-web-js/recipe/emailverification"; - -async function sendEmail() { - try { - let response = await sendVerificationEmail(); - if (response.status === "EMAIL_ALREADY_VERIFIED_ERROR") { - // This can happen if the info about email verification in the session was outdated. - // Redirect the user to the home page - window.location.assign("/home"); - } else { - // email was sent successfully. - window.alert("Please check your email and click the link in it") - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - -```tsx -import supertokensEmailVerification from "supertokens-web-js/recipe/emailverification"; - -async function sendEmail() { - try { - let response = await supertokensEmailVerification.sendVerificationEmail(); - if (response.status === "EMAIL_ALREADY_VERIFIED_ERROR") { - // This can happen if the info about email verification in the session was outdated. - // Redirect the user to the home page - window.location.assign("/home"); - } else { - // email was sent successfully. - window.alert("Please check your email and click the link in it") - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - - - - - - - -You should create a new screen on your app that asks the user to enter their email to which an email will be sent. This screen should ideally be linked to from the sign in form. - -Once the user has enters their email, you can call the following API to send an email verification email to that user: - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/user/email/verify/token' \ ---header 'Authorization: Bearer ...' -``` - - - -The response body from the API call has a `status` property in it: -- `status: "OK"`: An email was sent to the user successfully. -- `status: "EMAIL_ALREADY_VERIFIED_ERROR"`: This status can be returned if the info about email verification in the session was outdated. Redirect the user to the home page. -- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. - - - -You do not need to add the tenant ID to the path here because the backend fetches the tenantId of the user from the session token. - - - - - - -:::note -The API for sending an email verification email requires an active session. If you are using our frontend SDKs, then the session tokens should automatically get attached to the request. -::: - -### Changing the email verification link domain / path -By default, the email verification link will point to the `websiteDomain` that is configured on the backend, on the `/auth/verify-email` route (where `/auth` is the default value of `websiteBasePath`). - -If you want to change this to a different path, a different domain, or deep link it to your mobile / desktop app, then you can do so on the backend in the following way: - - - - -```tsx -import SuperTokens from "supertokens-node"; -import EmailVerification from "supertokens-node/recipe/emailverification"; - -SuperTokens.init({ - supertokens: { - connectionURI: "...", - }, - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "..." - }, - recipeList: [ - EmailVerification.init({ - mode: "OPTIONAL", - // highlight-start - emailDelivery: { - override: (originalImplementation) => { - return { - ...originalImplementation, - sendEmail(input) { - return originalImplementation.sendEmail({ - ...input, - emailVerifyLink: input.emailVerifyLink.replace( - // This is: `${websiteDomain}${websiteBasePath}/verify-email` - "http://localhost:3000/auth/verify-email", - "http://localhost:3000/your/path" - ) - } - ) - }, - } - } - } - // highlight-end - }) - ] -}); -``` - - - -```go -import ( - "strings" - - "github.com/supertokens/supertokens-golang/ingredients/emaildelivery" - "github.com/supertokens/supertokens-golang/recipe/emailverification" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - supertokens.Init(supertokens.TypeInput{ - RecipeList: []supertokens.Recipe{ - emailverification.Init(evmodels.TypeInput{ - Mode: evmodels.ModeOptional, - // highlight-start - EmailDelivery: &emaildelivery.TypeInput{ - Override: func(originalImplementation emaildelivery.EmailDeliveryInterface) emaildelivery.EmailDeliveryInterface { - ogSendEmail := *originalImplementation.SendEmail - - (*originalImplementation.SendEmail) = func(input emaildelivery.EmailType, userContext supertokens.UserContext) error { - // This is: `${websiteDomain}${websiteBasePath}/verify-email` - input.EmailVerification.EmailVerifyLink = strings.Replace( - input.EmailVerification.EmailVerifyLink, - "http://localhost:3000/auth/verify-email", - "http://localhost:3000/your/path", 1, - ) - return ogSendEmail(input, userContext) - } - return originalImplementation - }, - }, - // highlight-end - }), - }, - }) -} -``` - - - -```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import emailverification -from supertokens_python.ingredients.emaildelivery.types import EmailDeliveryConfig -from supertokens_python.recipe.emailverification.types import EmailDeliveryOverrideInput, EmailTemplateVars -from typing import Dict, Any - - -def custom_email_delivery(original_implementation: EmailDeliveryOverrideInput) -> EmailDeliveryOverrideInput: - original_send_email = original_implementation.send_email - - # highlight-start - async def send_email(template_vars: EmailTemplateVars, user_context: Dict[str, Any]) -> None: - - # This is: `${websiteDomain}${websiteBasePath}/verify-email` - template_vars.email_verify_link = template_vars.email_verify_link.replace( - "http://localhost:3000/auth/verify-email", "http://localhost:3000/your/path") - - return await original_send_email(template_vars, user_context) - # highlight-end - - original_implementation.send_email = send_email - return original_implementation - - -init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore - recipe_list=[ - emailverification.init( - mode="OPTIONAL", - # highlight-next-line - email_delivery=EmailDeliveryConfig(override=custom_email_delivery)) - ] -) -``` - - - - - - - -For a multi tenant setup, the input to the `sendEmail` function will also contain the `tenantId`. You can use this to determine the correct value to set for the websiteDomain in the generated link. - - - -## Step 6: Verifying the email post link clicked - -Once the user clicks the email verification link, and it opens your app, you can call the following function which will automatically extract the token and tenantId (if using a multi tenant setup) from the link and call the token verification API. - - - - - - - -```tsx -import { verifyEmail } from "supertokens-web-js/recipe/emailverification"; - -async function consumeVerificationCode() { - try { - let response = await verifyEmail(); - if (response.status === "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR") { - // This can happen if the verification code is expired or invalid. - // You should ask the user to retry - window.alert("Oops! Seems like the verification link expired. Please try again") - window.location.assign("/auth/verify-email") // back to the email sending screen. - } else { - // email was verified successfully. - window.location.assign("/home") - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - -```tsx -import supertokensEmailVerification from "supertokens-web-js/recipe/emailverification"; - -async function consumeVerificationCode() { - try { - let response = await supertokensEmailVerification.verifyEmail(); - if (response.status === "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR") { - // This can happen if the verification code is expired or invalid. - // You should ask the user to retry - window.alert("Oops! Seems like the verification link expired. Please try again") - window.location.assign("/auth/verify-email") // back to the email sending screen. - } else { - // email was verified successfully. - window.location.assign("/home") - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - - - - - - - -Once the user clicks the email verification link, and it opens as a deep link into your mobile app, you can extract the token from the link and call the verification API as shown below: - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/user/email/verify' \ ---header 'Content-Type: application/json; charset=utf-8' \ ---data-raw '{ - "method": "token", - "token": "ZTRiOTBjNz...jI5MTZlODkxw" -}' -``` - - - - -For a multi tenancy setup, the `` value can be fetched from `tenantId` query parameter from the email verification link. If it's not there in the link, you can use the value `"public"` (which is the default tenant). - - - - - - -The response body from the API call has a `status` property in it: -- `status: "OK"`: Email verification was successful. -- `status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR"`: This can happen if the verification code is expired or invalid. You should ask the user to retry. -- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. - - - - -:::caution -- This API doesn't require an active session to succeed. -- If you are calling the above API on page load, there is an edge case in which email clients might open the verification link in the email (for scanning purposes) and consume the token in the URL. This would lead to issues in which an attacker could sign up using someone else's email and end up with a veriifed status! - - To prevent this, on page load, you should check if a session exists, and if it does, only then call the above API. If a session does not exist, you should first show a button, which when clicked would call the above API (email clients won't automatically click on this button). The button text could be something like "Click here to verify your email". -::: - - - - - - - -## See also - -- [Post email verification action](../common-customizations/email-verification/handling-email-verification-success) -- [Change email verification link's lifetime](../common-customizations/email-verification/changing-token-lifetime) -- [Customise email template or email delivery method](../email-delivery/about) -- [Manually changing email verification status for a user](../common-customizations/email-verification/changing-email-verification-status) -- [Generating email verification links manually](../common-customizations/email-verification/generate-link-manually) -- 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) in `REQUIRED` mode. - - \ No newline at end of file diff --git a/v2/thirdpartyemailpassword/custom-ui/handling-session-tokens.mdx b/v2/thirdpartyemailpassword/custom-ui/handling-session-tokens.mdx index bdfa1a21f..6ff934e75 100644 --- a/v2/thirdpartyemailpassword/custom-ui/handling-session-tokens.mdx +++ b/v2/thirdpartyemailpassword/custom-ui/handling-session-tokens.mdx @@ -4,412 +4,7 @@ title: Handling session tokens hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -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"; -import AppInfoForm from "/src/components/appInfoForm" -import {Question, Answer}from "/src/components/question" + -# Handling session tokens - -There are two modes ways in which you can use sessions with SuperTokens: -- Using `httpOnly` cookies -- Authorization bearer token. - -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 - -### For Web - - - - - -:::success -No action required. -::: - -Our frontend SDK handles everything for you. You only need to make sure that you have called `supertokens.init` before making any network requests. - -Our SDK adds interceptors to `fetch` and `XHR` (used by `axios`) to save and add session tokens from and to the request. - -By default, our web SDKs use cookies to provide credentials. - - - - - -import NetworkInterceptors from "/session/reusableMD/networkInterceptors.mdx" - -### For React-Native -Our frontend SDK handles everything for you. You only need to make sure that you have added our network interceptors as shown below - - - -:::note -By default our mobile SDKs use a bearer token in the Authorization header to provide credentials. -::: - -### For Android - - - - -```kotlin -import android.app.Application -import com.supertokens.session.SuperTokens -import com.supertokens.session.SuperTokensHttpURLConnection -import com.supertokens.session.SuperTokensPersistentCookieStore -import java.net.URL -import java.net.HttpURLConnection - -class MainApplication: Application() { - override fun onCreate() { - super.onCreate() - // TODO: Make sure to call SuperTokens.init - } - - fun makeRequest() { - val url = URL("") - val connection = SuperTokensHttpURLConnection.newRequest(url, object: SuperTokensHttpURLConnection.PreConnectCallback { - override fun doAction(con: HttpURLConnection?) { - // TODO: Use `con` to set request method, headers etc - } - }) - - // Handle response using connection object, for example: - if (connection.responseCode == 200) { - // TODO: implement - } - } -} -``` - -:::note -When making network requests you do not need to call `HttpURLConnection.connect` because SuperTokens does this for you. -::: - - - - -```kotlin -import android.content.Context -import com.supertokens.session.SuperTokens -import com.supertokens.session.SuperTokensInterceptor -import okhttp3.OkHttpClient -import retrofit2.Retrofit - -class NetworkManager { - fun getClient(context: Context): OkHttpClient { - val clientBuilder = OkHttpClient.Builder() - clientBuilder.addInterceptor(SuperTokensInterceptor()) - // TODO: Make sure to call SuperTokens.init - - val client = clientBuilder.build() - - // REQUIRED FOR RETROFIT ONLY - val instance = Retrofit.Builder() - .baseUrl("") - .client(client) - .build() - - return client - } - - fun makeRequest(context: Context) { - val client = getClient(context) - // Use client to make requests normally - } -} -``` - - - - -:::note -By default our mobile SDKs use a bearer token in the Authorization header to provide credentials. -::: - -### For iOS - - - - - -

      Using URLSession.shared

      - -```swift -import Foundation -import SuperTokensIOS - -fileprivate class NetworkManager { - func setupSuperTokensInterceptor() { - URLProtocol.registerClass(SuperTokensURLProtocol.self) - } -} -``` - -

      Using a custom URLSession instance

      - -```swift -import Foundation -import SuperTokensIOS - -fileprivate class NetworkManager { - func setupSuperTokensInterceptor() { - let configuration = URLSessionConfiguration.default - configuration.protocolClasses = [SuperTokensURLProtocol.self] - let session = URLSession(configuration: configuration) - - // Use session when making network requests - } -} -``` - -
      - - - -```swift -import Foundation -import SuperTokensIOS -import Alamofire - -fileprivate class NetworkManager { - func setupSuperTokensInterceptor() { - let configuration = URLSessionConfiguration.af.default - configuration.protocolClasses = [SuperTokensURLProtocol.self] + (configuration.protocolClasses ?? []) - let session = Session(configuration: configuration) - - // Use session when making network requests - } -} -``` - - - -
      - -:::note -By default our mobile SDKs use a bearer token in the Authorization header to provide credentials. -::: - -### For Flutter - - - - -You can make requests as you normally would with `http`, the only difference is that you import the client from the supertokens package instead. - -```dart -// Import http from the SuperTokens package -import 'package:supertokens_flutter/http.dart' as http; - -Future makeRequest() async { - Uri uri = Uri.parse("http://localhost:3001/api"); - var response = await http.get(uri); - // handle response -} -``` - -

      Using a custom http client

      - -If you use a custom http client and want to use SuperTokens, you can simply provide the SDK with your client. All requests will continue to use your client along with the session logic that SuperTokens provides. - -```dart -// Import http from the SuperTokens package -import 'package:supertokens_flutter/http.dart' as http; - -Future makeRequest() async { - Uri uri = Uri.parse("http://localhost:3001/api"); - - // Initialise your custom client - var customClient = http.Client(); - // provide your custom client to SuperTokens - var httpClient = http.Client(client: customClient); - - var response = await httpClient.get(uri); - // handle response -} -``` - -
      - - -

      Add the SuperTokens interceptor

      - -Use the extension method provided by the SuperTokens SDK to enable interception on your Dio client. This allows the SuperTokens SDK to handle session tokens for you. - -```dart -import 'package:supertokens_flutter/dio.dart'; -import 'package:dio/dio.dart'; - -void setup() { - Dio dio = Dio(); // Create a Dio instance. - dio.addSupertokensInterceptor(); -} -``` - -

      Making network requests

      - -You can make requests as you normally would with `dio`. - -```dart -import 'package:supertokens_flutter/dio.dart'; -import 'package:dio/dio.dart'; - -void setup() { - Dio dio = Dio( - // Provide your config here - ); - dio.addSupertokensInterceptor(); - - var response = dio.get("http://localhost:3001/api"); - // handle response -} -``` - -
      -
      - -:::note -By default our mobile SDKs use a bearer token in the Authorization header to provide credentials. -::: - -## If not using our frontend SDK - -:::caution -We highly recommend using our frontend SDK to handle session token management. It will save you a lot of time. -::: - -In this case, you will need to manually handle the tokens and session refreshing, and decide if you are going to use header or cookie-based sessions. - -For browsers, we recommend cookies, while for mobile apps (or if you don't want to use the built-in cookie manager) you should use header-based sessions. - - - - - -### On login - -You should attach the `st-auth-mode` header to calls to the login API, but this header is safe to attach to all requests. In this case it should be set to "cookie". - -The login API will return the following headers: -- `Set-Cookie`: This will contain the `sAccessToken`, `sRefreshToken` cookies which will be `httpOnly` and will be automatically mananaged by the browser. For mobile apps, you will need to setup cookie handling yourself, use our SDK or use a header based authentication mode. -- `front-token` header: This contains information about the access token: - - The userID - - The expiry time of the access token - - The payload added by you in the access token. - - Here is the structure of the token: - ```tsx - let frontTokenFromRequestHeader = "..."; - let frontTokenDecoded = JSON.parse(decodeURIComponent(escape(atob(frontTokenFromRequestHeader)))); - console.log(frontTokenDecoded); - /* - { - ate: 1665226412455, // time in milliseconds for when the access token will expire, and then a refresh is required - uid: "....", // user ID - up: { - sub: "..", - iat: .., - ... // other access token payload - } - } - - */ - ``` - - This token is mainly used for cookie based auth because you don't have access to the actual access token on the frontend (for security reasons), but may want to read its payload (for example to know the user's role). This token itself is not signed and hence can't be used in place of the access token itself. You may want to save this token in localstorage or in frontend cookies (using `document.cookies`). - -- `anti-csrf` header (optional): By default it's not required, so it's not sent. But if this is sent, you should save this token as well for use when making requests. - -### Making network requests to protected APIs - -The `sAccessToken` will get attached to the request automatically by the browser. Other than that, you need to add the following headers to the request: -- `rid: "anti-csrf"` - this prevents against anti-CSRF requests. If your `apiDomain` and `websiteDomain` values are exactly the same, then this is not necessary. -- `anti-csrf` header (optional): If this was provided to you during login, then you need to add that token as the value of this header. -- You need to set the `credentials` header to `true` or `include`. This is achieved different based on the library you are using to make requests. - -An API call can potentially update the `sAccessToken` and `front-token` tokens, for example if you call the `mergeIntoAccessTokenPayload` function on the `session` object on the backend. This kind of update is reflected in the response headers for your API calls. The headers will contain new values for: -- `sAccessToken`: This will be as a new `Set-Cookie` header and will be managed by the browser automatically. -- `front-token`: This should be read and saved by you in the same way as it's being done during login. - -### Handling session refreshing -If any of your API calls return with a status code of `401`, it means that the access token has expired. This will require you to refresh the session before retrying the same API call. - -You can call the refresh API as follows: - - - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/session/refresh' \ ---header 'Cookie: sRefreshToken=...' -``` - -:::note -- You may also need to add the `anti-csrf` header to the request if that was provided to you during sign in. -- The cURL command above shows the `sRefreshToken` cookie as well, but this is added by the web browser automatically, so you don't need to add it explicitly. -::: - - - -The result of a session refresh will be either: -- Status code `200`: This implies a succesful refresh. The set of tokens returned here will be the same as when the user logs in, so you can handle them in the same way. -- Status code `401`: This means that the refresh token is invalid, or has been revoked. You must ask the user to relogin. Remember to clear the `front-token` that you saved on the frontend earlier. - - - - - -### On login - -You should attach the `st-auth-mode` header to calls to the login API, but this header is safe to attach to all requests. In this case it should be set to "header". - -The login API will return the following headers: -- `st-access-token`: This contains the current access token associated with the session. You should save this in your application (e.g., in frontend localstorage). -- `st-refresh-token`: This contains the current refresh token associated with the session. You should save this in your application (e.g., in frontend localstorage). - -### Making network requests to protected APIs - -You need to add the following headers to request: -- `authorization: Bearer {access-token}` -- You need to set the `credentials` to `true` or `include`. This is achieved different based on the library you are using to make requests. - -An API call can potentially update the `access-token`, for example if you call the `mergeIntoAccessTokenPayload` function on the `session` object on the backend. This kind of update is reflected in the response headers for your API calls. The headers will contain new values for `st-access-token` - -These should be read and saved by you in the same way as it's being done during login. - -### Handling session refreshing -If any of your API calls return with a status code of `401`, it means that the access token has expired. This will require you to refresh the session before retrying the same API call. - -You can call the refresh API as follows: - - - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/session/refresh' \ ---header 'authorization: Bearer {refresh-token}' -``` - - - -The result of a session refresh will be either: -- Status code `200`: This implies a succesful refresh. The set of tokens returned here will be the same as when the user logs in, so you can handle them in the same way. -- Status code `401`: This means that the refresh token is invalid, or has been revoked. You must ask the user to relogin. Remember to clear the `st-refresh-token` and `st-access-token` that you saved on the frontend earlier. - - - - diff --git a/v2/thirdpartyemailpassword/custom-ui/init/backend.mdx b/v2/thirdpartyemailpassword/custom-ui/init/backend.mdx index ab3ec4bd8..577fc5a32 100644 --- a/v2/thirdpartyemailpassword/custom-ui/init/backend.mdx +++ b/v2/thirdpartyemailpassword/custom-ui/init/backend.mdx @@ -4,1166 +4,8 @@ title: "Step 2: Backend" hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import TabItem from '@theme/TabItem'; -import NodeJSFrameworkSubTabsServerless from "/src/components/tabs/NodeJSFrameworkSubTabsServerless" -import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" -import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs" -import BackendSDKCasing from "/src/components/BackendSDKCasing" -import AppInfoForm from "/src/components/appInfoForm" -import CoreInjector from "/src/components/coreInjector" -import {Question, Answer}from "/src/components/question" -# Backend Integration + -## Supported frameworks - -import TechStackSupport from "../../../community/reusableMD/supported-tech-stacks-backend.mdx" - - - -## 1) Install - - - - -```bash -npm i -s supertokens-node -``` - - - - -```bash -go get github.com/supertokens/supertokens-golang -``` - - - - - -```bash -pip install supertokens-python -``` - - - - -## 2) Initialise SuperTokens - - - - - -Add the code below to your server's init file. - - - - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import EmailPassword from"supertokens-node/recipe/emailpassword"; -import ThirdParty from"supertokens-node/recipe/thirdparty"; - -supertokens.init({ - framework: "express", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/thirdpartyemailpassword/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - EmailPassword.init(), - ThirdParty.init({/*TODO: See next step*/}), - Session.init() // initializes session features - ] -}); -``` - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import EmailPassword from"supertokens-node/recipe/emailpassword"; -import ThirdParty from"supertokens-node/recipe/thirdparty"; - -supertokens.init({ - framework: "hapi", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - EmailPassword.init(), - ThirdParty.init({/*TODO: See next step*/}), - Session.init() // initializes session features - ] -}); -``` - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import EmailPassword from"supertokens-node/recipe/emailpassword"; -import ThirdParty from"supertokens-node/recipe/thirdparty"; - -supertokens.init({ - framework: "fastify", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - EmailPassword.init(), - ThirdParty.init({/*TODO: See next step*/}), - Session.init() // initializes session features - ] -}); -``` - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import EmailPassword from"supertokens-node/recipe/emailpassword"; -import ThirdParty from"supertokens-node/recipe/thirdparty"; - -supertokens.init({ - framework: "koa", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - EmailPassword.init(), - ThirdParty.init({/*TODO: See next step*/}), - Session.init() // initializes session features - ] -}); -``` - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import EmailPassword from"supertokens-node/recipe/emailpassword"; -import ThirdParty from"supertokens-node/recipe/thirdparty"; - -supertokens.init({ - framework: "loopback", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - EmailPassword.init(), - ThirdParty.init({/*TODO: See next step*/}), - Session.init() // initializes session features - ] -}); -``` - - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/emailpassword" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/thirdparty" - "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - apiBasePath := "^{form_apiBasePath}" - websiteBasePath := "^{form_websiteBasePath}" - err := supertokens.Init(supertokens.TypeInput{ - Supertokens: &supertokens.ConnectionInfo{ - ^{coreInjector_connection_uri_comment} - ConnectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}APIKey: ^{coreInjector_api_key}, - }, - AppInfo: supertokens.AppInfo{ - AppName: "^{form_appName}", - APIDomain: "^{form_apiDomain}", - WebsiteDomain: "^{form_websiteDomain}", - APIBasePath: &apiBasePath, - WebsiteBasePath: &websiteBasePath, - }, - RecipeList: []supertokens.Recipe{ - thirdparty.Init(&tpmodels.TypeInput{ /*TODO: See next step*/ }), - emailpassword.Init(nil), - session.Init(nil), // initializes session features - }, - }) - - if err != nil { - panic(err.Error()) - } -} -``` - - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig -from supertokens_python.recipe import thirdparty, emailpassword, session - -init( - app_info=InputAppInfo( - app_name="^{form_appName}", - api_domain="^{form_apiDomain}", - website_domain="^{form_websiteDomain}", - api_base_path="^{form_apiBasePath}", - website_base_path="^{form_websiteBasePath}" - ), - supertokens_config=SupertokensConfig( - ^{coreInjector_connection_uri_comment_with_hash} - connection_uri=^{coreInjector_uri} - ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} - ), - framework='fastapi', - recipe_list=[ - session.init(), # initializes session features - thirdparty.init( - # TODO: See next step - ), - emailpassword.init() - ], - mode='asgi' # use wsgi if you are running using gunicorn -) -``` - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig -from supertokens_python.recipe import thirdparty, emailpassword, session - -init( - app_info=InputAppInfo( - app_name="^{form_appName}", - api_domain="^{form_apiDomain}", - website_domain="^{form_websiteDomain}", - api_base_path="^{form_apiBasePath}", - website_base_path="^{form_websiteBasePath}" - ), - supertokens_config=SupertokensConfig( - ^{coreInjector_connection_uri_comment_with_hash} - connection_uri=^{coreInjector_uri} - ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} - ), - framework='flask', - recipe_list=[ - session.init(), # initializes session features - thirdparty.init( - # TODO: See next step - ), - emailpassword.init() - ] -) -``` - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig -from supertokens_python.recipe import thirdparty, emailpassword, session - -init( - app_info=InputAppInfo( - app_name="^{form_appName}", - api_domain="^{form_apiDomain}", - website_domain="^{form_websiteDomain}", - api_base_path="^{form_apiBasePath}", - website_base_path="^{form_websiteBasePath}" - ), - supertokens_config=SupertokensConfig( - ^{coreInjector_connection_uri_comment_with_hash} - connection_uri=^{coreInjector_uri} - ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} - ), - framework='django', - recipe_list=[ - session.init(), # initializes session features - thirdparty.init( - # TODO: See next step - ), - emailpassword.init() - ], - mode='asgi' # use wsgi if you are running django server in sync mode -) -``` - - - - - - - - - - -## 3) Initialise Social login providers - - - -Populate the `providers` array with the third party auth providers you want. - - - - -```tsx -import SuperTokens from "supertokens-node"; -import ThirdParty from "supertokens-node/recipe/thirdparty" -import EmailPassword from "supertokens-node/recipe/emailpassword" - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - EmailPassword.init(), - ThirdParty.init({ - //highlight-start - // We have provided you with development keys which you can use for testing. - // IMPORTANT: Please replace them with your own OAuth keys for production use. - signInAndUpFeature: { - providers: [{ - config: { - thirdPartyId: "google", - clients: [{ - clientId: "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", - clientSecret: "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" - }] - } - }, { - config: { - thirdPartyId: "github", - clients: [{ - clientId: "467101b197249757c71f", - clientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd" - }] - } - }, { - config: { - thirdPartyId: "apple", - clients: [{ - clientId: "4398792-io.supertokens.example.service", - additionalConfig: { - keyId: "7M48Y4RYDL", - privateKey: - "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----", - teamId: "YWQCXGJRJL", - } - }] - } - }], - } - //highlight-end - }), - // ... - ] -}); -``` - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/thirdparty" - "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" -) - -func main() { - - // Inside supertokens.Init -> RecipeList - thirdparty.Init(&tpmodels.TypeInput{ - // highlight-start - SignInAndUpFeature: tpmodels.TypeInputSignInAndUp{ - Providers: []tpmodels.ProviderInput{ - // We have provided you with development keys which you can use for testing. - // IMPORTANT: Please replace them with your own OAuth keys for production use. - { - Config: tpmodels.ProviderConfig{ - ThirdPartyId: "google", - Clients: []tpmodels.ProviderClientConfig{ - { - ClientID: "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", - ClientSecret: "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", - }, - }, - }, - }, - { - Config: tpmodels.ProviderConfig{ - ThirdPartyId: "github", - Clients: []tpmodels.ProviderClientConfig{ - { - ClientID: "467101b197249757c71f", - ClientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd", - }, - }, - }, - }, - { - Config: tpmodels.ProviderConfig{ - ThirdPartyId: "apple", - Clients: []tpmodels.ProviderClientConfig{ - { - ClientID: "4398792-io.supertokens.example.service", - AdditionalConfig: map[string]interface{}{ - "keyId": "7M48Y4RYDL", - "privateKey": "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----", - "teamId": "YWQCXGJRJL", - }, - }, - }, - }, - }, - }, - }, - // highlight-end - }) -} -``` - - - -```python -from supertokens_python.recipe.thirdparty.provider import ProviderInput, ProviderConfig, ProviderClientConfig -from supertokens_python.recipe import thirdparty - -# Inside init -thirdparty.init( - # highlight-start - sign_in_and_up_feature=thirdparty.SignInAndUpFeature( - providers=[ - # We have provided you with development keys which you can use for testing. - # IMPORTANT: Please replace them with your own OAuth keys for production use. - ProviderInput( - config=ProviderConfig( - third_party_id="google", - clients=[ - ProviderClientConfig( - client_id="1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", - client_secret="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", - ), - ], - ), - ), - ProviderInput( - config=ProviderConfig( - third_party_id="github", - clients=[ - ProviderClientConfig( - client_id='467101b197249757c71f', - client_secret='e97051221f4b6426e8fe8d51486396703012f5bd' - ), - ], - ), - ), - ProviderInput( - config=ProviderConfig( - third_party_id="apple", - clients=[ - ProviderClientConfig( - client_id="4398792-io.supertokens.example.service", - additional_config={ - "keyId": "7M48Y4RYDL", - "privateKey": "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----", - "teamId": "YWQCXGJRJL" - }, - ), - ], - ), - ), - ] - ) - # highlight-end -) -``` - - - - -**When you want to generate your own keys**, please refer to the corresponding documentation to get your client ids and client secrets for each of the below providers: - -
      -Google - -- Generate your client ID and secret by following the [docs here](https://support.google.com/cloud/answer/6158849?hl=en) -- Set the authorisation callback URL to `^{form_websiteDomain}^{form_websiteBasePathForCallbacks}/callback/google` - -
      - -
      -Github - -- Generate your client ID and secret by following the [docs here](https://docs.github.com/en/developers/apps/creating-an-oauth-app) -- Set the authorisation callback URL to `^{form_websiteDomain}^{form_websiteBasePathForCallbacks}/callback/github` - -
      - -
      -Apple - -- Generate your client ID and secret by following [this article](https://medium.com/identity-beyond-borders/how-to-configure-sign-in-with-apple-77c61e336003) -- Set the authorisation callback URL to `^{form_apiDomain}^{form_apiBasePathForCallbacks}/callback/apple`. Note that Apple doesn't allow `localhost` in the URL. So if you are in dev mode, you can use the dev keys we have provided above. - -
      - -:::important -You can find the list of built in providers [here](../../common-customizations/signup-form/built-in-providers). To add a provider that is not listed, you can follow our guide on [setting up custom providers](../../common-customizations/signup-form/custom-providers). -::: - -
      - - -## 4) Add the SuperTokens APIs & CORS setup - - - - - - - - -:::important -- Add the `middleware` BEFORE all your routes. -- Add the `cors` middleware BEFORE the SuperTokens middleware as shown below. -::: - -```tsx -import express from "express"; -import cors from "cors"; -import supertokens from "supertokens-node"; -import {middleware} from "supertokens-node/framework/express"; - -let app = express(); - -app.use(cors({ - // highlight-start - origin: "^{form_websiteDomain}", - allowedHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], - credentials: true, - // highlight-end -})); - -// IMPORTANT: CORS should be before the below line. -// highlight-next-line -app.use(middleware()); - -// ...your API routes -``` -This `middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): - -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. -- `POST ^{form_apiBasePath}/signup`: For signing up a user with email & password -- `POST ^{form_apiBasePath}/signin`: For signing in a user with email & password - - - - -Register the `plugin`. - -```tsx -import Hapi from "@hapi/hapi"; -import supertokens from "supertokens-node"; -import { plugin } from "supertokens-node/framework/hapi"; - -let server = Hapi.server({ - port: 8000, - routes: { - // highlight-start - cors: { - origin: ["^{form_websiteDomain}"], - additionalHeaders: [...supertokens.getAllCORSHeaders()], - credentials: true, - } - // highlight-end - } -}); - -(async () => { - // highlight-next-line - await server.register(plugin); - - await server.start(); -})(); - -// ...your API routes -``` -This `plugin` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)) as well take care of all the errors thrown by the Supertokens library: - -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. -- `POST ^{form_apiBasePath}/signup`: For signing up a user with email & password -- `POST ^{form_apiBasePath}/signin`: For signing in a user with email & password - - - - -Register the `plugin`. Also register [`@fastify/formbody`](https://github.com/fastify/fastify-formbody) plugin. - -```tsx -import cors from "@fastify/cors"; -import supertokens from "supertokens-node"; -import { plugin } from "supertokens-node/framework/fastify"; -import formDataPlugin from "@fastify/formbody"; - -import fastifyImport from "fastify"; - -let fastify = fastifyImport(); - -// ...other middlewares -// highlight-start -fastify.register(cors, { - origin: "^{form_websiteDomain}", - allowedHeaders: ['Content-Type', ...supertokens.getAllCORSHeaders()], - credentials: true, -}); -// highlight-end - -(async () => { - // highlight-next-line - await fastify.register(formDataPlugin); - // highlight-next-line - await fastify.register(plugin); - - await fastify.listen(8000); -})(); - -// ...your API routes -``` -This `plugin` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): - -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. -- `POST ^{form_apiBasePath}/signup`: For signing up a user with email & password -- `POST ^{form_apiBasePath}/signin`: For signing in a user with email & password - - - - -:::important -Add the `middleware` BEFORE all your routes. -::: - -```tsx -import cors from '@koa/cors'; -import Koa from "koa"; -import supertokens from "supertokens-node"; -import { middleware } from "supertokens-node/framework/koa"; - -let app = new Koa(); - -// ...other middlewares -app.use(cors({ - // highlight-start - origin: "^{form_websiteDomain}", - allowHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], - credentials: true, - // highlight-end -})); - -// highlight-next-line -app.use(middleware()); - -// ...your API routes -``` -This `middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): - -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. -- `POST ^{form_apiBasePath}/signup`: For signing up a user with email & password -- `POST ^{form_apiBasePath}/signin`: For signing in a user with email & password - - - - -:::important -Add the `middleware` BEFORE all your routes. -::: - -```tsx -import supertokens from "supertokens-node"; -import { middleware } from "supertokens-node/framework/loopback"; -import { RestApplication } from '@loopback/rest'; - -let app = new RestApplication({ - rest: { - cors: { - // highlight-start - origin: "^{form_websiteDomain}", - allowedHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], - credentials: true - // highlight-end - } - } -}); - -// highlight-next-line -app.middleware(middleware); - -// ...your API routes -``` -This `middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): - -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. -- `POST ^{form_apiBasePath}/signup`: For signing up a user with email & password -- `POST ^{form_apiBasePath}/signin`: For signing in a user with email & password - - - - - - - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "net/http" - "strings" - - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - //highlight-start - http.ListenAndServe("SERVER ADDRESS", corsMiddleware( - supertokens.Middleware(http.HandlerFunc(func(rw http.ResponseWriter, - //highlight-end - r *http.Request) { - // TODO: Handle your APIs.. - - })))) -} - -func corsMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(response http.ResponseWriter, r *http.Request) { - response.Header().Set("Access-Control-Allow-Origin", "^{form_websiteDomain}") - response.Header().Set("Access-Control-Allow-Credentials", "true") - if r.Method == "OPTIONS" { - // we add content-type + other headers used by SuperTokens - response.Header().Set("Access-Control-Allow-Headers", - strings.Join(append([]string{"Content-Type"}, - //highlight-start - supertokens.GetAllCORSHeaders()...), ",")) - //highlight-end - response.Header().Set("Access-Control-Allow-Methods", "*") - response.Write([]byte("")) - } else { - next.ServeHTTP(response, r) - } - }) -} -``` - - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "net/http" - - "github.com/gin-contrib/cors" - "github.com/gin-gonic/gin" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - router := gin.New() - - // CORS - router.Use(cors.New(cors.Config{ - AllowOrigins: []string{"^{form_websiteDomain}"}, - AllowMethods: []string{"GET", "POST", "DELETE", "PUT", "OPTIONS"}, - AllowHeaders: append([]string{"content-type"}, - // highlight-next-line - supertokens.GetAllCORSHeaders()...), - AllowCredentials: true, - })) - - // Adding the SuperTokens middleware - // highlight-start - router.Use(func(c *gin.Context) { - supertokens.Middleware(http.HandlerFunc( - func(rw http.ResponseWriter, r *http.Request) { - c.Next() - })).ServeHTTP(c.Writer, c.Request) - // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly - c.Abort() - }) - // highlight-end - - // Add APIs and start server -} -``` - - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "github.com/go-chi/chi" - "github.com/go-chi/cors" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - r := chi.NewRouter() - - // CORS - r.Use(cors.Handler(cors.Options{ - AllowedOrigins: []string{"^{form_websiteDomain}"}, - AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, - AllowedHeaders: append([]string{"Content-Type"}, - //highlight-next-line - supertokens.GetAllCORSHeaders()...), - AllowCredentials: true, - })) - - // SuperTokens Middleware - //highlight-next-line - r.Use(supertokens.Middleware) - - // Add APIs and start server -} -``` - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "net/http" - - "github.com/gorilla/handlers" - "github.com/gorilla/mux" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - // Add APIs - - router := mux.NewRouter() - - // Adding handlers.CORS(options)(supertokens.Middleware(router))) - //highlight-start - http.ListenAndServe("SERVER ADDRESS", handlers.CORS( - handlers.AllowedHeaders(append([]string{"Content-Type"}, - supertokens.GetAllCORSHeaders()...)), - handlers.AllowedMethods([]string{"GET", "POST", "PUT", "HEAD", "OPTIONS"}), - handlers.AllowedOrigins([]string{"^{form_websiteDomain}"}), - handlers.AllowCredentials(), - )(supertokens.Middleware(router))) - //highlight-end -} -``` - - - -This `Middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): - -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. -- `POST ^{form_apiBasePath}/signup`: For signing up a user with email & password -- `POST ^{form_apiBasePath}/signin`: For signing in a user with email & password - - - - - - -Use the `Middleware` (**BEFORE all your routes**) and the `get_all_cors_headers()` functions as shown below. - -```python -from supertokens_python import get_all_cors_headers -from fastapi import FastAPI -from starlette.middleware.cors import CORSMiddleware -from supertokens_python.framework.fastapi import get_middleware - -app = FastAPI() -# highlight-next-line -app.add_middleware(get_middleware()) - -# TODO: Add APIs - -app.add_middleware( - CORSMiddleware, - allow_origins=[ - "^{form_websiteDomain}" - ], - allow_credentials=True, - allow_methods=["GET", "PUT", "POST", "DELETE", "OPTIONS", "PATCH"], - # highlight-next-line - allow_headers=["Content-Type"] + get_all_cors_headers(), -) - -# TODO: start server -``` - - - - -- Use the `Middleware` (**BEFORE all your routes and after calling init function**) and the `get_all_cors_headers()` functions as shown below. -- Add a route to catch all paths and return a 404. This is needed because if we don't add this, then OPTIONS request for the APIs exposed by the `Middleware` will return a `404`. - -```python -from supertokens_python import get_all_cors_headers -from flask import Flask, abort -from flask_cors import CORS # type: ignore -from supertokens_python.framework.flask import Middleware - -app = Flask(__name__) -# highlight-next-line -Middleware(app) - -# TODO: Add APIs - -CORS( - app=app, - origins=[ - "^{form_websiteDomain}" - ], - supports_credentials=True, - # highlight-next-line - allow_headers=["Content-Type"] + get_all_cors_headers(), -) - -# This is required since if this is not there, then OPTIONS requests for -# the APIs exposed by the supertokens' Middleware will return a 404 -# highlight-start -@app.route('/', defaults={'u_path': ''}) # type: ignore -@app.route('/') # type: ignore -def catch_all(u_path: str): - abort(404) -# highlight-end - -# TODO: start server -``` - - - - -Use the `Middleware` and the `get_all_cors_headers()` functions as shown below in your `settings.py`. - -```python -from supertokens_python import get_all_cors_headers -from typing import List -from corsheaders.defaults import default_headers - -CORS_ORIGIN_WHITELIST = [ - "^{form_websiteDomain}" -] - -CORS_ALLOW_CREDENTIALS = True - -CORS_ALLOWED_ORIGINS = [ - "^{form_websiteDomain}" -] - -CORS_ALLOW_HEADERS: List[str] = list(default_headers) + [ - "Content-Type" - # highlight-next-line -] + get_all_cors_headers() - -INSTALLED_APPS = [ - 'corsheaders', - 'supertokens_python' -] - -MIDDLEWARE = [ - 'corsheaders.middleware.CorsMiddleware', - ..., - # highlight-next-line - 'supertokens_python.framework.django.django_middleware.middleware', -] -``` - - - - -This `Middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): - -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. -- `POST ^{form_apiBasePath}/signup`: For signing up a user with email & password -- `POST ^{form_apiBasePath}/signin`: For signing in a user with email & password - - - - - - -## 5) Add the SuperTokens error handler - - - - - - - -```tsx -import { errorHandler } from "supertokens-node/framework/express"; -import express from "express"; -let app = express(); -// ...your API routes - -// highlight-start -// Add this AFTER all your routes -app.use(errorHandler()) -// highlight-end - -// your own error handler -app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => { - // Your error handler logic -}); -``` - - - -No additional `errorHandler` is required. - - - - -Add the `errorHandler` **Before all your routes and plugin registration** - -```tsx -import { errorHandler } from "supertokens-node/framework/fastify"; -import fastify from 'fastify' -const server = fastify() - -// highlight-next-line -server.setErrorHandler(errorHandler()); - -// ...your API routes - -``` - - - -No additional `errorHandler` is required. - - - - -No additional `errorHandler` is required. - - - - - - - -:::info -You can skip this step -::: - - - - -:::info -You can skip this step -::: - - - - -## 6) Setup the SuperTokens core - -You need to now setup an instance of the SuperTokens core for your app (that your backend should connect to). You have two options: -- [Managed service](./core/saas-setup) -- Self hosted with your own database ([With Docker](./core/with-docker) or [Without Docker](./core/without-docker)) diff --git a/v2/thirdpartyemailpassword/custom-ui/init/core/managed-service.mdx b/v2/thirdpartyemailpassword/custom-ui/init/core/managed-service.mdx index 5dade6aca..726e4dc4a 100644 --- a/v2/thirdpartyemailpassword/custom-ui/init/core/managed-service.mdx +++ b/v2/thirdpartyemailpassword/custom-ui/init/core/managed-service.mdx @@ -4,95 +4,9 @@ title: Managed Service hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import TabItem from '@theme/TabItem'; -# Managed Service + -## Creating a development environment ✨ -- First, please [sign up](https://supertokens.com/auth) -- Select the auth method you want to use and follow the guided steps to integrate with our frontend and backend SDK if you have not done this already -Integration with SuperTokens SDKs -- Select a region and click the deploy button: -:::tip -You should select a region that is closest to your backend. -::: - -Deploying SuperTokens Core - -- After the deployment is complete the dashboard will look similar to this: -Deployed SuperTokens Core - -## Connecting the backend SDK with SuperTokens 🔌 -- Add the `connectionURI` and the `apiKey` shown on the dashboard to your code on the backend. - -SuperTokens managed service dashboard connectionURI and API key - - - - -```tsx -import supertokens from "supertokens-node"; - -supertokens.init({ - // highlight-start - supertokens: { - connectionURI: "", - apiKey: "" - }, - // highlight-end - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "..." - }, - recipeList: [] -}); -``` - - - - -```go -import "github.com/supertokens/supertokens-golang/supertokens" - -func main() { - supertokens.Init(supertokens.TypeInput{ - // highlight-start - Supertokens: &supertokens.ConnectionInfo{ - ConnectionURI: "", - APIKey: "", - }, - // highlight-end - }) -} - -``` - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig - -init( - app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), - # highlight-start - supertokens_config=SupertokensConfig( - connection_uri='', - api_key='' - ), - # highlight-end - framework='...', # type: ignore - recipe_list=[ - #... - ] -) -``` - - - diff --git a/v2/thirdpartyemailpassword/custom-ui/init/core/self-hosted-with-docker.mdx b/v2/thirdpartyemailpassword/custom-ui/init/core/self-hosted-with-docker.mdx index 1df643ac0..e31d7f6e6 100644 --- a/v2/thirdpartyemailpassword/custom-ui/init/core/self-hosted-with-docker.mdx +++ b/v2/thirdpartyemailpassword/custom-ui/init/core/self-hosted-with-docker.mdx @@ -4,268 +4,8 @@ title: With Docker hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import DatabaseTabs from "/src/components/tabs/DatabaseTabs" -import TabItem from '@theme/TabItem'; -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import DockerVersionProvider from "/src/components/dockerVersionProvider"; -# With Docker + -## Running the docker image 🚀 - - - - - - -```bash -docker run -p 3567:3567 -d registry.supertokens.io/supertokens/supertokens-mysql^{docker_version_mysql} -``` - -- To see all the env variables available, please see [the README file](https://github.com/supertokens/supertokens-docker-mysql/blob/master/README.md). -- The above command will start the container with an in-memory database. This means you **do not need to connect it to MySQL to test out SuperTokens**. -- When you are ready to connect it to your database, please visit the [Database setup section](../database-setup/mysql) - - - - - -```bash -docker run -p 3567:3567 -d registry.supertokens.io/supertokens/supertokens-postgresql^{docker_version_postgresql} -``` - -- To see all the env variables available, please see [the README file](https://github.com/supertokens/supertokens-docker-postgresql/blob/master/README.md). -- The above command will start the container with an in-memory database. This means you **do not need to connect it to PostgreSQL to test out SuperTokens**. -- When you are ready to connect it to your database, please visit the [Database setup section](../database-setup/postgresql) - - - - - -```bash -docker run -p 3567:3567 -d registry.supertokens.io/supertokens/supertokens-mongodb^{docker_version_mongodb} -``` - -- To see all the env variables available, please see [the README file](https://github.com/supertokens/supertokens-docker-mongodb/blob/master/README.md). -- The above command will start the container with an in-memory database. This means you **do not need to connect it to MongoDB to test out SuperTokens**. -- When you are ready to connect it to your database, please visit the [Database setup section](../database-setup/mongodb) - -:::caution -We do not offer login functionality with MongDB yet. We only offer session management. -::: - - - - - -## Testing that the service is running 🤞 -Open a browser and visit `http://localhost:3567/hello`. If you see a page that says `Hello` back, then the container was started successfully! - -If you are having issues with starting the docker image, please feel free to reach out to us [over email](mailto:team@supertokens.com) or [via Discord](https://supertokens.com/discord). - -:::tip -The `/hello` route checks whether the database connection is set up correctly and will only return a 200 status code if there is no issue. - -If you are using kubernetes or docker swarm, this endpoint is perfect for doing readiness and liveness probes. -::: - -## Connecting the backend SDK with SuperTokens 🔌 -- The default `port` for SuperTokens is `3567`. You can change this by binding a different port in the `docker run` command. For example, `docker run -p 8080:3567` will run SuperTokens on port `8080` on your machine. -- The connection info will go in the `supertokens` object in the `init` function on your backend: - - - - -```tsx -import supertokens from "supertokens-node"; - -supertokens.init({ - // highlight-start - supertokens: { - connectionURI: "http://localhost:3567", - apiKey: "someKey" // OR can be undefined - }, - // highlight-end - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "..." - }, - recipeList: [] -}); -``` - - - - -```go -import "github.com/supertokens/supertokens-golang/supertokens" - -func main() { - supertokens.Init(supertokens.TypeInput{ - // highlight-start - Supertokens: &supertokens.ConnectionInfo{ - ConnectionURI: "http://localhost:3567", - APIKey: "someKey", - }, - // highlight-end - }) -} - -``` - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig - -init( - app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), - # highlight-start - supertokens_config=SupertokensConfig( - connection_uri='http://localhost:3567', - api_key='someKey' - ), - # highlight-end - framework='...', # type: ignore - recipe_list=[ - #... - ] -) -``` - - - - -:::tip Security -There is no API key by default. Visit the "Auth flow customization" -> "SuperTokens core settings" -> "Adding API Keys" section to see how to add one. -::: - -## Docker compose file - - - - - - -```bash -version: '3' - -services: - db: - image: mysql:latest - environment: - MYSQL_ROOT_PASSWORD: root - MYSQL_USER: supertokens_user - MYSQL_PASSWORD: somePassword - MYSQL_DATABASE: supertokens - ports: - - 3306:3306 - networks: - - app_network - restart: unless-stopped - healthcheck: - test: [ "CMD", "mysqladmin", "ping", "-h", "localhost" ] - timeout: 20s - retries: 10 - - supertokens: - image: registry.supertokens.io/supertokens/supertokens-mysql^{docker_version_mysql} - depends_on: - db: - condition: service_healthy - ports: - - 3567:3567 - environment: - MYSQL_CONNECTION_URI: mysql://supertokens_user:somePassword@db:3306/supertokens - networks: - - app_network - restart: unless-stopped - healthcheck: - test: > - bash -c 'exec 3<>/dev/tcp/127.0.0.1/3567 && echo -e "GET /hello HTTP/1.1\r\nhost: 127.0.0.1:3567\r\nConnection: close\r\n\r\n" >&3 && cat <&3 | grep "Hello"' - interval: 10s - timeout: 5s - retries: 5 - -networks: - app_network: - driver: bridge -``` - - - - - -```bash -version: '3' - -services: - # Note: If you are assigning a custom name to your db service on the line below, make sure it does not contain underscores - db: - image: 'postgres:latest' - environment: - POSTGRES_USER: supertokens_user - POSTGRES_PASSWORD: somePassword - POSTGRES_DB: supertokens - ports: - - 5432:5432 - networks: - - app_network - restart: unless-stopped - healthcheck: - test: ['CMD', 'pg_isready', '-U', 'supertokens_user', '-d', 'supertokens'] - interval: 5s - timeout: 5s - retries: 5 - - supertokens: - image: registry.supertokens.io/supertokens/supertokens-postgresql^{docker_version_postgresql} - depends_on: - db: - condition: service_healthy - ports: - - 3567:3567 - environment: - POSTGRESQL_CONNECTION_URI: "postgresql://supertokens_user:somePassword@db:5432/supertokens" - networks: - - app_network - restart: unless-stopped - healthcheck: - test: > - bash -c 'exec 3<>/dev/tcp/127.0.0.1/3567 && echo -e "GET /hello HTTP/1.1\r\nhost: 127.0.0.1:3567\r\nConnection: close\r\n\r\n" >&3 && cat <&3 | grep "Hello"' - interval: 10s - timeout: 5s - retries: 5 - -networks: - app_network: - driver: bridge -``` - - - - - - -We are working on adding this section. - - - - - - -:::important -If you are running the backend process that integrates with our backend sdk as part of the docker compose file as well, make sure to use `http://supertokens:3567` as the connection uri instead of `http://localhost:3567`. -::: - - -## Helm charts for Kubernetes - -- For [MySQL image](https://github.com/supertokens/supertokens-docker-mysql/tree/master/helm-chart) - -- For [PostgreSQL image](https://github.com/supertokens/supertokens-docker-postgresql/tree/master/helm-chart) diff --git a/v2/thirdpartyemailpassword/custom-ui/init/core/self-hosted-without-docker.mdx b/v2/thirdpartyemailpassword/custom-ui/init/core/self-hosted-without-docker.mdx index 9a44a4e8c..7068a55cb 100644 --- a/v2/thirdpartyemailpassword/custom-ui/init/core/self-hosted-without-docker.mdx +++ b/v2/thirdpartyemailpassword/custom-ui/init/core/self-hosted-without-docker.mdx @@ -4,165 +4,9 @@ title: Without Docker hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import OSTabs from "/src/components/tabs/OSTabs"; -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -# Binary Installation + -## 1) Download SuperTokens -- Visit the [open source download page](https://supertokens.com/use-oss). -- Click on the "Binary" tab. -- Choose your database. -- Download the SuperTokens zip file for your OS. - -Once downloaded, extract the zip, and you will see a folder named `supertokens`. - -## 2) Install SuperTokens - - - - -```bash -# sudo is required so that the supertokens -# command can be added to your PATH variable. - -cd supertokens -sudo ./install -``` - - - - -```bash - -cd supertokens -./install - -``` -:::caution -You may get an error like `java cannot be opened because the developer cannot be verified`. To solve this, visit System Preferences > Security & Privacy > General Tab, and then click on the Allow button at the bottom. Then retry the command above. -::: - - - - - -```batch - -Rem run as an Administrator. This is required so that the supertokens -Rem command can be added to your PATH. - -cd supertokens -install.bat - -``` - - - -:::important -After installing, you can delete the downloaded folder as you no longer need it. - -Any changes to the the config will be done in the `config.yaml` file in the installation directory, the location of which is specified in the output of the `supertokens --help` command. -::: - -## 3) Start SuperTokens 🚀 -Running the following command will start the service. -```bash -supertokens start [--host=...] [--port=...] -``` -- The above command will start the container with an in-memory database. -- When you are ready to connect it to your database, please visit the [Database setup section](../database-setup/mysql) -- To see all available options please run `supertokens start --help` - - -## 4) Testing that the service is running 🤞 -Open a browser and visit `http://localhost:3567/hello`. If you see a page that says `Hello` back, then SuperTokens was started successfully! - -If you are having issues with starting the docker image, please feel free to reach out to us [over email](mailto:team@supertokens.com) or [via Discord](https://supertokens.com/discord). - - -## 5) Stopping SuperTokens 🛑 -```bash -supertokens stop -``` - -## Connecting the backend SDK with SuperTokens 🔌 -- The default `host` and `port` for SuperTokens is `localhost:3567`. You can change this by passing `--host` and `--port` options to the `start` command. -- The connection info will go in the `supertokens` object in the `init` function on your backend: - - - - - -```tsx -import supertokens from "supertokens-node"; - -supertokens.init({ - // highlight-start - supertokens: { - connectionURI: "http://localhost:3567", - apiKey: "someKey" // OR can be undefined - }, - // highlight-end - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "..." - }, - recipeList: [] -}); - -``` - - - - -```go -import "github.com/supertokens/supertokens-golang/supertokens" - -func main() { - supertokens.Init(supertokens.TypeInput{ - // highlight-start - Supertokens: &supertokens.ConnectionInfo{ - ConnectionURI: "http://localhost:3567", - APIKey: "someKey", - }, - // highlight-end - }) -} - -``` - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig - -init( - app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), - # highlight-start - supertokens_config=SupertokensConfig( - connection_uri='http://localhost:3567', - api_key='someKey' - ), - # highlight-end - framework='...', # type: ignore - recipe_list=[ - #... - ] -) -``` - - - - -:::tip Security -There is no API key by default. Visit the "Auth flow customization" -> "SuperTokens core settings" -> "Adding API Keys" section to see how to add one. -::: diff --git a/v2/thirdpartyemailpassword/custom-ui/init/database-setup/mysql.mdx b/v2/thirdpartyemailpassword/custom-ui/init/database-setup/mysql.mdx index eb37456b2..3597e3e64 100644 --- a/v2/thirdpartyemailpassword/custom-ui/init/database-setup/mysql.mdx +++ b/v2/thirdpartyemailpassword/custom-ui/init/database-setup/mysql.mdx @@ -4,533 +4,7 @@ title: If using MySQL hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import SelfHostedTabs from "/src/components/tabs/SelfHostedTabs" -import TabItem from '@theme/TabItem'; + -# MySQL setup - -:::important -This is needed only if you are running the SuperTokens core yourself. - -The minimum required version is **MySQL 5.7**. -::: - -:::tip blog -We also have [a blog post writeup](https://supertokens.com/blog/connect-supertokens-to-database) highlighting all the steps in more detail for different scenarios. -::: - -## 1) Create a database 🛠️ -```sql -CREATE DATABASE supertokens; -``` -You can skip this step if you want SuperTokens to write to your own database. In this case, you will need to provide your database's name as shown in the step below. - -## 2) Connect SuperTokens to your database 🔌 - - - - -:::caution -Host being `localhost` / `127.0.0.1` will not work in a docker image. Instead, please provide the database's local / public hostname or IP address. - -You also need to make the database listen on all the IP's of the local machine. -This can be done by editing the `mysqld.cnf` config file and setting the value of `bind-address` to `0.0.0.0`. -::: - -```bash - -docker run \ - -p 3567:3567 \ -//highlight-next-line - -e MYSQL_CONNECTION_URI="mysql://username:pass@host/dbName" \ - -d registry.supertokens.io/supertokens/supertokens-mysql - -# OR - -docker run \ - -p 3567:3567 \ -//highlight-start - -e MYSQL_USER="username" \ - -e MYSQL_PASSWORD="password" \ - -e MYSQL_HOST="host" \ - -e MYSQL_PORT="3306" \ - -e MYSQL_DATABASE_NAME="supertokens" \ -//highlight-end - -d registry.supertokens.io/supertokens/supertokens-mysql -``` - - - - - - - -```yaml -# You need to add the following to the config.yaml file. -# The file path can be found by running the "supertokens --help" command - -mysql_connection_uri: "mysql://username:pass@host/dbName" - -# OR - -mysql_user: "username" - -mysql_password: "password" - -mysql_host: "host" - -mysql_port: 3306 - -mysql_database_name: "supertokens" -``` - - - - -## 3) Create tables 👩‍💻👨‍💻 - -:::note -This happens automatically, unless you provide a MySQL user that doesn't have table creation permission. -::: - -```sql -CREATE TABLE `apps` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `created_at_time` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`) -); - -CREATE TABLE `tenants` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `created_at_time` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `tenant_configs` ( - `connection_uri_domain` varchar(256) NOT NULL DEFAULT '', - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `core_config` text, - `email_password_enabled` tinyint(1) DEFAULT NULL, - `passwordless_enabled` tinyint(1) DEFAULT NULL, - `third_party_enabled` tinyint(1) DEFAULT NULL, - `is_first_factors_null` tinyint(1) DEFAULT NULL, - PRIMARY KEY (`connection_uri_domain`,`app_id`,`tenant_id`) -); - -CREATE TABLE `tenant_thirdparty_providers` ( - `connection_uri_domain` varchar(256) NOT NULL DEFAULT '', - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `third_party_id` varchar(28) NOT NULL, - `name` varchar(64) DEFAULT NULL, - `authorization_endpoint` text, - `authorization_endpoint_query_params` text, - `token_endpoint` text, - `token_endpoint_body_params` text, - `user_info_endpoint` text, - `user_info_endpoint_query_params` text, - `user_info_endpoint_headers` text, - `jwks_uri` text, - `oidc_discovery_endpoint` text, - `require_email` tinyint(1) DEFAULT NULL, - `user_info_map_from_id_token_payload_user_id` varchar(64) DEFAULT NULL, - `user_info_map_from_id_token_payload_email` varchar(64) DEFAULT NULL, - `user_info_map_from_id_token_payload_email_verified` varchar(64) DEFAULT NULL, - `user_info_map_from_user_info_endpoint_user_id` varchar(64) DEFAULT NULL, - `user_info_map_from_user_info_endpoint_email` varchar(64) DEFAULT NULL, - `user_info_map_from_user_info_endpoint_email_verified` varchar(64) DEFAULT NULL, - PRIMARY KEY (`connection_uri_domain`,`app_id`,`tenant_id`,`third_party_id`), - FOREIGN KEY (`connection_uri_domain`, `app_id`, `tenant_id`) REFERENCES `tenant_configs` (`connection_uri_domain`, `app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE TABLE `tenant_thirdparty_provider_clients` ( - `connection_uri_domain` varchar(256) NOT NULL DEFAULT '', - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `third_party_id` varchar(28) NOT NULL, - `client_type` varchar(64) NOT NULL DEFAULT '', - `client_id` varchar(256) NOT NULL, - `client_secret` text, - `scope` text, - `force_pkce` tinyint(1) DEFAULT NULL, - `additional_config` text, - PRIMARY KEY (`connection_uri_domain`,`app_id`,`tenant_id`,`third_party_id`,`client_type`), - FOREIGN KEY (`connection_uri_domain`, `app_id`, `tenant_id`, `third_party_id`) REFERENCES `tenant_thirdparty_providers` (`connection_uri_domain`, `app_id`, `tenant_id`, `third_party_id`) ON DELETE CASCADE -); - -CREATE TABLE `tenant_first_factors` ( - connection_uri_domain VARCHAR(256) DEFAULT '', - app_id VARCHAR(64) DEFAULT 'public', - tenant_id VARCHAR(64) DEFAULT 'public', - factor_id VARCHAR(128), - PRIMARY KEY (`connection_uri_domain`, `app_id`, `tenant_id`, `factor_id`), - FOREIGN KEY (`connection_uri_domain`, `app_id`, `tenant_id`) REFERENCES `tenant_configs` (`connection_uri_domain`, `app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE TABLE `tenant_required_secondary_factors` ( - connection_uri_domain VARCHAR(256) DEFAULT '', - app_id VARCHAR(64) DEFAULT 'public', - tenant_id VARCHAR(64) DEFAULT 'public', - factor_id VARCHAR(128), - PRIMARY KEY (`connection_uri_domain`, `app_id`, `tenant_id`, `factor_id`), - FOREIGN KEY (`connection_uri_domain`, `app_id`, `tenant_id`) REFERENCES `tenant_configs` (`connection_uri_domain`, `app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE TABLE `key_value` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `name` varchar(128) NOT NULL, - `value` text, - `created_at_time` bigint unsigned DEFAULT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`name`), - FOREIGN KEY (`app_id`, `tenant_id`) REFERENCES `tenants` (`app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE TABLE `app_id_to_user_id` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `recipe_id` varchar(128) NOT NULL, - `primary_or_recipe_user_id` char(36) NOT NULL, - `is_linked_or_is_a_primary_user` BOOLEAN NOT NULL DEFAULT FALSE, - PRIMARY KEY (`app_id`,`user_id`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE, - FOREIGN KEY (`app_id`, `primary_or_recipe_user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE INDEX app_id_to_user_id_primary_user_id_index ON `app_id_to_user_id` (`primary_or_recipe_user_id`); - -CREATE INDEX app_id_to_user_id_user_id_index ON `app_id_to_user_id` (`user_id`); - -CREATE TABLE `all_auth_recipe_users` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `primary_or_recipe_user_id` char(36) NOT NULL, - `is_linked_or_is_a_primary_user` BOOLEAN NOT NULL DEFAULT FALSE, - `recipe_id` varchar(128) NOT NULL, - `time_joined` bigint unsigned NOT NULL, - `primary_or_recipe_user_time_joined` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`user_id`), - KEY `app_id` (`app_id`,`user_id`), - FOREIGN KEY (`app_id`, `tenant_id`) REFERENCES `tenants` (`app_id`, `tenant_id`) ON DELETE CASCADE, - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE, - FOREIGN KEY (`app_id`, `primary_or_recipe_user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE INDEX all_auth_recipe_users_pagination_index1 ON all_auth_recipe_users - (app_id, tenant_id, primary_or_recipe_user_time_joined DESC, primary_or_recipe_user_id DESC); - -CREATE INDEX all_auth_recipe_users_pagination_index3 ON all_auth_recipe_users - (recipe_id, app_id, tenant_id, primary_or_recipe_user_time_joined DESC, primary_or_recipe_user_id DESC); - -CREATE INDEX all_auth_recipe_users_primary_user_id_index ON all_auth_recipe_users - (primary_or_recipe_user_id, app_id); - -CREATE INDEX all_auth_recipe_users_recipe_id_index ON all_auth_recipe_users - (app_id, recipe_id, tenant_id); - -CREATE TABLE `userid_mapping` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `supertokens_user_id` char(36) NOT NULL, - `external_user_id` varchar(128) NOT NULL, - `external_user_id_info` text, - PRIMARY KEY (`app_id`,`supertokens_user_id`,`external_user_id`), - UNIQUE KEY `supertokens_user_id` (`app_id`,`supertokens_user_id`), - UNIQUE KEY `external_user_id` (`app_id`,`external_user_id`), - FOREIGN KEY (`app_id`, `supertokens_user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE TABLE `dashboard_users` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `email` varchar(256) NOT NULL, - `password_hash` varchar(256) NOT NULL, - `time_joined` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`user_id`), - UNIQUE KEY `email` (`app_id`,`email`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `dashboard_user_sessions` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `session_id` char(36) NOT NULL, - `user_id` char(36) NOT NULL, - `time_created` bigint unsigned NOT NULL, - `expiry` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`session_id`), - KEY `app_id` (`app_id`,`user_id`), - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `dashboard_users` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE INDEX `dashboard_user_sessions_expiry_index` ON `dashboard_user_sessions` (`expiry`); - -CREATE TABLE `session_access_token_signing_keys` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `created_at_time` bigint unsigned NOT NULL, - `value` text, - PRIMARY KEY (`app_id`,`created_at_time`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `session_info` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `session_handle` varchar(255) NOT NULL, - `user_id` varchar(128) NOT NULL, - `refresh_token_hash_2` varchar(128) NOT NULL, - `session_data` text, - `expires_at` bigint unsigned NOT NULL, - `created_at_time` bigint unsigned NOT NULL, - `jwt_user_payload` text, - `use_static_key` tinyint(1) NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`session_handle`), - FOREIGN KEY (`app_id`, `tenant_id`) REFERENCES `tenants` (`app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE INDEX `session_expiry_index` ON `session_info` (`expires_at`); - -CREATE TABLE `user_last_active` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - `last_active_time` bigint unsigned DEFAULT NULL, - PRIMARY KEY (`app_id`,`user_id`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `emailpassword_users` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `email` varchar(256) NOT NULL, - `password_hash` varchar(256) NOT NULL, - `time_joined` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`user_id`), - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE TABLE `emailpassword_user_to_tenant` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `email` varchar(256) NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`user_id`), - UNIQUE KEY `email` (`app_id`,`tenant_id`,`email`), - FOREIGN KEY (`app_id`, `tenant_id`, `user_id`) REFERENCES `all_auth_recipe_users` (`app_id`, `tenant_id`, `user_id`) ON DELETE CASCADE -); - -CREATE TABLE `emailpassword_pswd_reset_tokens` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `token` varchar(128) NOT NULL, - `token_expiry` bigint unsigned NOT NULL, - `email` varchar(256), - PRIMARY KEY (`app_id`,`user_id`,`token`), - UNIQUE KEY `token` (`token`), - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE ON UPDATE CASCADE -); - -CREATE INDEX `emailpassword_password_reset_token_expiry_index` ON `emailpassword_pswd_reset_tokens` (`token_expiry`); - -CREATE TABLE `emailverification_verified_emails` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - `email` varchar(256) NOT NULL, - PRIMARY KEY (`app_id`,`user_id`,`email`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `emailverification_tokens` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - `email` varchar(256) NOT NULL, - `token` varchar(128) NOT NULL, - `token_expiry` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`user_id`,`email`,`token`), - UNIQUE KEY `token` (`token`), - FOREIGN KEY (`app_id`, `tenant_id`) REFERENCES `tenants` (`app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE INDEX `emailverification_tokens_index` ON `emailverification_tokens` (`token_expiry`); - -CREATE TABLE `thirdparty_users` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `third_party_id` varchar(28) NOT NULL, - `third_party_user_id` varchar(256) NOT NULL, - `user_id` char(36) NOT NULL, - `email` varchar(256) NOT NULL, - `time_joined` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`user_id`), - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE INDEX `thirdparty_users_email_index` ON `thirdparty_users` (`app_id`,`email`); - -CREATE INDEX `thirdparty_users_thirdparty_user_id_index` ON `thirdparty_users` (`app_id`,`third_party_id`,`third_party_user_id`); - -CREATE TABLE `thirdparty_user_to_tenant` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `third_party_id` varchar(28) NOT NULL, - `third_party_user_id` varchar(256) NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`user_id`), - UNIQUE KEY `third_party_user_id` (`app_id`,`tenant_id`,`third_party_id`,`third_party_user_id`), - FOREIGN KEY (`app_id`, `tenant_id`, `user_id`) REFERENCES `all_auth_recipe_users` (`app_id`, `tenant_id`, `user_id`) ON DELETE CASCADE -); - -CREATE TABLE `passwordless_users` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `email` varchar(256) DEFAULT NULL, - `phone_number` varchar(256) DEFAULT NULL, - `time_joined` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`user_id`), - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE TABLE `passwordless_user_to_tenant` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `email` varchar(256) DEFAULT NULL, - `phone_number` varchar(256) DEFAULT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`user_id`), - UNIQUE KEY `email` (`app_id`,`tenant_id`,`email`), - UNIQUE KEY `phone_number` (`app_id`,`tenant_id`,`phone_number`), - FOREIGN KEY (`app_id`, `tenant_id`, `user_id`) REFERENCES `all_auth_recipe_users` (`app_id`, `tenant_id`, `user_id`) ON DELETE CASCADE -); - -CREATE TABLE `passwordless_devices` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `device_id_hash` char(44) NOT NULL, - `email` varchar(256) DEFAULT NULL, - `phone_number` varchar(256) DEFAULT NULL, - `link_code_salt` char(44) NOT NULL, - `failed_attempts` int unsigned NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`device_id_hash`), - FOREIGN KEY (`app_id`, `tenant_id`) REFERENCES `tenants` (`app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE INDEX `passwordless_devices_email_index` ON `passwordless_devices` (`app_id`,`tenant_id`,`email`); - -CREATE INDEX `passwordless_devices_phone_number_index` ON `passwordless_devices` (`app_id`,`tenant_id`,`phone_number`); - -CREATE TABLE `passwordless_codes` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `code_id` char(36) NOT NULL, - `device_id_hash` char(44) NOT NULL, - `link_code_hash` char(44) NOT NULL, - `created_at` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`code_id`), - UNIQUE KEY `link_code_hash` (`app_id`,`tenant_id`,`link_code_hash`), - KEY `app_id` (`app_id`,`tenant_id`,`device_id_hash`), - FOREIGN KEY (`app_id`, `tenant_id`, `device_id_hash`) REFERENCES `passwordless_devices` (`app_id`, `tenant_id`, `device_id_hash`) ON DELETE CASCADE ON UPDATE CASCADE -); - -CREATE INDEX `passwordless_codes_created_at_index` ON `passwordless_codes` (`app_id`,`tenant_id`,`created_at`); - -CREATE TABLE `jwt_signing_keys` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `key_id` varchar(255) NOT NULL, - `key_string` text NOT NULL, - `algorithm` varchar(10) NOT NULL, - `created_at` bigint unsigned DEFAULT NULL, - PRIMARY KEY (`app_id`,`key_id`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `user_metadata` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - `user_metadata` text NOT NULL, - PRIMARY KEY (`app_id`,`user_id`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `roles` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `role` varchar(255) NOT NULL, - PRIMARY KEY (`app_id`,`role`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `role_permissions` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `role` varchar(255) NOT NULL, - `permission` varchar(255) NOT NULL, - PRIMARY KEY (`app_id`,`role`,`permission`), - FOREIGN KEY (`app_id`, `role`) REFERENCES `roles` (`app_id`, `role`) ON DELETE CASCADE -); - -CREATE INDEX `role_permissions_permission_index` ON `role_permissions` (`app_id`,`permission`); - -CREATE TABLE `user_roles` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - `role` varchar(255) NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`user_id`,`role`), - FOREIGN KEY (`app_id`, `tenant_id`) REFERENCES `tenants` (`app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE INDEX `user_roles_role_index` ON `user_roles` (`app_id`,`tenant_id`,`role`); - -CREATE TABLE `totp_users` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - PRIMARY KEY (`app_id`,`user_id`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `totp_user_devices` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - `device_name` varchar(256) NOT NULL, - `secret_key` varchar(256) NOT NULL, - `period` int NOT NULL, - `skew` int NOT NULL, - `verified` tinyint(1) NOT NULL, - `created_at` BIGINT UNSIGNED, - PRIMARY KEY (`app_id`,`user_id`,`device_name`), - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `totp_users` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE TABLE `totp_used_codes` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - `code` varchar(8) NOT NULL, - `is_valid` tinyint(1) NOT NULL, - `expiry_time_ms` bigint unsigned NOT NULL, - `created_time_ms` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`user_id`,`created_time_ms`), - KEY `app_id` (`app_id`,`user_id`), - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `totp_users` (`app_id`, `user_id`) ON DELETE CASCADE, - FOREIGN KEY (`app_id`, `tenant_id`) REFERENCES `tenants` (`app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE INDEX `totp_used_codes_expiry_time_ms_index` ON `totp_used_codes` (`app_id`,`tenant_id`,`expiry_time_ms`); -``` - -:::tip -You also have the option to [rename these tables](./rename-database-tables). -::: - -## 4) Test the connection 🤞 -To test, start SuperTokens and run the following query in your database -```sql -SELECT * FROM key_value; -``` -If you see at least one row, it means that the connection has been successfully completed! 🥳🎉 - -:::tip blog -We also have [a blog post writeup](https://supertokens.com/blog/connect-supertokens-to-database) highlighting all the steps in more detail for different scenarios. -::: diff --git a/v2/thirdpartyemailpassword/custom-ui/init/database-setup/postgresql.mdx b/v2/thirdpartyemailpassword/custom-ui/init/database-setup/postgresql.mdx index abaea10fc..8375aa15f 100644 --- a/v2/thirdpartyemailpassword/custom-ui/init/database-setup/postgresql.mdx +++ b/v2/thirdpartyemailpassword/custom-ui/init/database-setup/postgresql.mdx @@ -4,601 +4,8 @@ title: If using PostgreSQL hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import SelfHostedTabs from "/src/components/tabs/SelfHostedTabs" -import TabItem from '@theme/TabItem'; -# PostgreSQL setup + -:::important -This is needed only if you are running the SuperTokens core yourself. - -The minimum required version is **PostgreSQL 9.6**. -::: - -:::tip blog -We also have [a blog post writeup](https://supertokens.com/blog/connect-supertokens-to-database) highlighting all the steps in more detail for different scenarios. -::: - -## 1) Create a database 🛠️ - -```sql -CREATE DATABASE supertokens; -``` -You can skip this step if you want SuperTokens to write to your own database. In this case, you will need to provide your database name as shown in the step below. - -## 2) Connect SuperTokens to your database 🔌 - - - - -:::caution -Host being `localhost` / `127.0.0.1` will not work in a docker image. Instead, please provide the database's local / public hostname or IP address. - -You also need to make the database listen on all the IP's of the local machine. -This can be done by editing the `postgresql.conf` config file and setting the value of `listen_addresses` to `0.0.0.0`. -::: - -```bash - -docker run \ - -p 3567:3567 \ - // highlight-next-line - -e POSTGRESQL_CONNECTION_URI="postgresql://username:pass@host/dbName" \ - -d registry.supertokens.io/supertokens/supertokens-postgresql - -# OR - -docker run \ - -p 3567:3567 \ - // highlight-start - -e POSTGRESQL_USER="username" \ - -e POSTGRESQL_PASSWORD="password" \ - -e POSTGRESQL_HOST="host" \ - -e POSTGRESQL_PORT="5432" \ - -e POSTGRESQL_DATABASE_NAME="supertokens" \ - // highlight-end - -d registry.supertokens.io/supertokens/supertokens-postgresql -``` - -:::tip -You can also provide the table schema by providing the `POSTGRESQL_TABLE_SCHEMA` option. -::: - - - - - -```yaml -# You need to add the following to the config.yaml file. -# The file path can be found by running the "supertokens --help" command - -postgresql_connection_uri: "postgresql://username:pass@host/dbName" - -# OR - -postgresql_user: "username" - -postgresql_password: "password" - -postgresql_host: "host" - -postgresql_port: "5432" - -postgresql_database_name: "supertokens" -``` - -- You can also provide the table schema by providing the `postgresql_table_schema` option. - - - - - -## 3) Create tables 👩‍💻👨‍💻 - -:::note -This happens automatically, unless you provide a PostgreSQL user that doesn't have table creation permission. -::: - -```sql -CREATE TABLE apps ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - created_at_time BIGINT, - CONSTRAINT apps_pkey PRIMARY KEY (app_id) -); - -CREATE TABLE tenants ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - created_at_time BIGINT, - CONSTRAINT tenants_pkey PRIMARY KEY (app_id, tenant_id), - CONSTRAINT tenants_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX tenants_app_id_index ON tenants (app_id); - -CREATE TABLE tenant_configs ( - connection_uri_domain VARCHAR(256) DEFAULT '' NOT NULL, - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - core_config TEXT, - email_password_enabled BOOLEAN, - passwordless_enabled BOOLEAN, - third_party_enabled BOOLEAN, - is_first_factors_null BOOLEAN, - CONSTRAINT tenant_configs_pkey PRIMARY KEY (connection_uri_domain, app_id, tenant_id) -); - -CREATE TABLE tenant_thirdparty_providers ( - connection_uri_domain VARCHAR(256) DEFAULT '' NOT NULL, - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - third_party_id VARCHAR(28) NOT NULL, - name VARCHAR(64), - authorization_endpoint TEXT, - authorization_endpoint_query_params TEXT, - token_endpoint TEXT, - token_endpoint_body_params TEXT, - user_info_endpoint TEXT, - user_info_endpoint_query_params TEXT, - user_info_endpoint_headers TEXT, - jwks_uri TEXT, - oidc_discovery_endpoint TEXT, - require_email BOOLEAN, - user_info_map_from_id_token_payload_user_id VARCHAR(64), - user_info_map_from_id_token_payload_email VARCHAR(64), - user_info_map_from_id_token_payload_email_verified VARCHAR(64), - user_info_map_from_user_info_endpoint_user_id VARCHAR(64), - user_info_map_from_user_info_endpoint_email VARCHAR(64), - user_info_map_from_user_info_endpoint_email_verified VARCHAR(64), - CONSTRAINT tenant_thirdparty_providers_pkey PRIMARY KEY (connection_uri_domain, app_id, tenant_id, third_party_id), - CONSTRAINT tenant_thirdparty_providers_tenant_id_fkey FOREIGN KEY (connection_uri_domain, app_id, tenant_id) REFERENCES public.tenant_configs(connection_uri_domain, app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX tenant_thirdparty_providers_tenant_id_index ON tenant_thirdparty_providers (connection_uri_domain, app_id, tenant_id); - -CREATE TABLE tenant_thirdparty_provider_clients ( - connection_uri_domain VARCHAR(256) DEFAULT '' NOT NULL, - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - third_party_id VARCHAR(28) NOT NULL, - client_type VARCHAR(64) DEFAULT '' NOT NULL, - client_id VARCHAR(256) NOT NULL, - client_secret TEXT, - scope VARCHAR(128)[], - force_pkce BOOLEAN, - additional_config TEXT, - CONSTRAINT tenant_thirdparty_provider_clients_pkey PRIMARY KEY (connection_uri_domain, app_id, tenant_id, third_party_id, client_type), - CONSTRAINT tenant_thirdparty_provider_clients_third_party_id_fkey FOREIGN KEY (connection_uri_domain, app_id, tenant_id, third_party_id) REFERENCES public.tenant_thirdparty_providers(connection_uri_domain, app_id, tenant_id, third_party_id) ON DELETE CASCADE -); - -CREATE INDEX tenant_thirdparty_provider_clients_third_party_id_index ON tenant_thirdparty_provider_clients (connection_uri_domain, app_id, tenant_id, third_party_id); - -CREATE TABLE tenant_first_factors ( - connection_uri_domain VARCHAR(256) DEFAULT '' NOT NULL, - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - factor_id VARCHAR(128), - CONSTRAINT tenant_first_factors_pkey PRIMARY KEY (connection_uri_domain, app_id, tenant_id, factor_id), - CONSTRAINT tenant_first_factors_tenant_id_fkey FOREIGN KEY (connection_uri_domain, app_id, tenant_id) REFERENCES public.tenant_configs(connection_uri_domain, app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX IF NOT EXISTS tenant_first_factors_tenant_id_index ON tenant_first_factors (connection_uri_domain, app_id, tenant_id); - -CREATE TABLE tenant_required_secondary_factors ( - connection_uri_domain VARCHAR(256) DEFAULT '' NOT NULL, - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - factor_id VARCHAR(128), - CONSTRAINT tenant_required_secondary_factors_pkey PRIMARY KEY (connection_uri_domain, app_id, tenant_id, factor_id), - CONSTRAINT tenant_required_secondary_factors_tenant_id_fkey FOREIGN KEY (connection_uri_domain, app_id, tenant_id) REFERENCES public.tenant_configs(connection_uri_domain, app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX IF NOT EXISTS tenant_default_required_factor_ids_tenant_id_index ON tenant_required_secondary_factors (connection_uri_domain, app_id, tenant_id); - -CREATE TABLE key_value ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - name VARCHAR(128) NOT NULL, - value TEXT, - created_at_time BIGINT, - CONSTRAINT key_value_pkey PRIMARY KEY (app_id, tenant_id, name), - CONSTRAINT key_value_tenant_id_fkey FOREIGN KEY (app_id, tenant_id) REFERENCES public.tenants(app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX key_value_tenant_id_index ON key_value (app_id, tenant_id); - -CREATE TABLE app_id_to_user_id ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - recipe_id VARCHAR(128) NOT NULL, - primary_or_recipe_user_id CHAR(36) NOT NULL, - is_linked_or_is_a_primary_user BOOLEAN NOT NULL DEFAULT FALSE, - CONSTRAINT app_id_to_user_id_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT app_id_to_user_id_primary_or_recipe_user_id_fkey FOREIGN KEY(app_id, primary_or_recipe_user_id) REFERENCES app_id_to_user_id (app_id, user_id) ON DELETE CASCADE, - CONSTRAINT app_id_to_user_id_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX app_id_to_user_id_app_id_index ON app_id_to_user_id (app_id); - -CREATE INDEX app_id_to_user_id_primary_user_id_index ON app_id_to_user_id (primary_or_recipe_user_id, app_id); - -CREATE TABLE all_auth_recipe_users ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - primary_or_recipe_user_id CHAR(36) NOT NULL, - is_linked_or_is_a_primary_user BOOLEAN NOT NULL DEFAULT FALSE, - recipe_id VARCHAR(128) NOT NULL, - time_joined BIGINT NOT NULL, - primary_or_recipe_user_time_joined BIGINT NOT NULL, - CONSTRAINT all_auth_recipe_users_pkey PRIMARY KEY (app_id, tenant_id, user_id), - CONSTRAINT all_auth_recipe_users_tenant_id_fkey FOREIGN KEY (app_id, tenant_id) REFERENCES public.tenants(app_id, tenant_id) ON DELETE CASCADE, - CONSTRAINT all_auth_recipe_users_primary_or_recipe_user_id_fkey FOREIGN KEY(app_id, primary_or_recipe_user_id) REFERENCES public.app_id_to_user_id (app_id, user_id) ON DELETE CASCADE, - CONSTRAINT all_auth_recipe_users_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.app_id_to_user_id(app_id, user_id) ON DELETE CASCADE -); - -CREATE INDEX all_auth_recipe_users_pagination_index1 ON all_auth_recipe_users - (app_id, tenant_id, primary_or_recipe_user_time_joined DESC, primary_or_recipe_user_id DESC); - -CREATE INDEX all_auth_recipe_users_pagination_index2 ON all_auth_recipe_users - (app_id, tenant_id, primary_or_recipe_user_time_joined ASC, primary_or_recipe_user_id DESC); - -CREATE INDEX all_auth_recipe_users_pagination_index3 ON all_auth_recipe_users - (recipe_id, app_id, tenant_id, primary_or_recipe_user_time_joined DESC, primary_or_recipe_user_id DESC); - - -CREATE INDEX all_auth_recipe_users_pagination_index4 ON all_auth_recipe_users - (recipe_id, app_id, tenant_id, primary_or_recipe_user_time_joined ASC, primary_or_recipe_user_id DESC); - - -CREATE INDEX all_auth_recipe_users_primary_user_id_index ON all_auth_recipe_users - (primary_or_recipe_user_id, app_id); - -CREATE INDEX all_auth_recipe_users_recipe_id_index ON all_auth_recipe_users - (app_id, recipe_id, tenant_id); - -CREATE INDEX all_auth_recipe_user_id_index ON all_auth_recipe_users (app_id, user_id); - -CREATE INDEX all_auth_recipe_tenant_id_index ON all_auth_recipe_users (app_id, tenant_id); - -CREATE TABLE userid_mapping ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - supertokens_user_id character(36) NOT NULL, - external_user_id VARCHAR(128) NOT NULL, - external_user_id_info TEXT, - CONSTRAINT userid_mapping_external_user_id_key UNIQUE (app_id, external_user_id), - CONSTRAINT userid_mapping_pkey PRIMARY KEY (app_id, supertokens_user_id, external_user_id), - CONSTRAINT userid_mapping_supertokens_user_id_key UNIQUE (app_id, supertokens_user_id), - CONSTRAINT userid_mapping_supertokens_user_id_fkey FOREIGN KEY (app_id, supertokens_user_id) REFERENCES public.app_id_to_user_id(app_id, user_id) ON DELETE CASCADE -); - -CREATE INDEX userid_mapping_supertokens_user_id_index ON userid_mapping (app_id, supertokens_user_id); - -CREATE TABLE dashboard_users ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - email VARCHAR(256) NOT NULL, - password_hash VARCHAR(256) NOT NULL, - time_joined BIGINT NOT NULL, - CONSTRAINT dashboard_users_email_key UNIQUE (app_id, email), - CONSTRAINT dashboard_users_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT dashboard_users_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX dashboard_users_app_id_index ON dashboard_users (app_id); - -CREATE TABLE dashboard_user_sessions ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - session_id character(36) NOT NULL, - user_id character(36) NOT NULL, - time_created BIGINT NOT NULL, - expiry BIGINT NOT NULL, - CONSTRAINT dashboard_user_sessions_pkey PRIMARY KEY (app_id, session_id), - CONSTRAINT dashboard_user_sessions_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.dashboard_users(app_id, user_id) ON UPDATE CASCADE ON DELETE CASCADE -); - -CREATE INDEX dashboard_user_sessions_expiry_index ON dashboard_user_sessions (expiry); - -CREATE INDEX dashboard_user_sessions_user_id_index ON dashboard_user_sessions (app_id, user_id); - -CREATE TABLE session_access_token_signing_keys ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - created_at_time BIGINT NOT NULL, - value TEXT, - CONSTRAINT session_access_token_signing_keys_pkey PRIMARY KEY (app_id, created_at_time), - CONSTRAINT session_access_token_signing_keys_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX access_token_signing_keys_app_id_index ON session_access_token_signing_keys (app_id); - -CREATE TABLE session_info ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - session_handle VARCHAR(255) NOT NULL, - user_id VARCHAR(128) NOT NULL, - refresh_token_hash_2 VARCHAR(128) NOT NULL, - session_data TEXT, - expires_at BIGINT NOT NULL, - created_at_time BIGINT NOT NULL, - jwt_user_payload TEXT, - use_static_key BOOLEAN NOT NULL, - CONSTRAINT session_info_pkey PRIMARY KEY (app_id, tenant_id, session_handle), - CONSTRAINT session_info_tenant_id_fkey FOREIGN KEY (app_id, tenant_id) REFERENCES public.tenants(app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX session_expiry_index ON session_info (expires_at); - -CREATE INDEX session_info_tenant_id_index ON session_info (app_id, tenant_id); - -CREATE TABLE user_last_active ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - last_active_time BIGINT, - CONSTRAINT user_last_active_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT user_last_active_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX user_last_active_app_id_index ON user_last_active (app_id); - -CREATE TABLE emailpassword_users ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - email VARCHAR(256) NOT NULL, - password_hash VARCHAR(256) NOT NULL, - time_joined BIGINT NOT NULL, - CONSTRAINT emailpassword_users_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT emailpassword_users_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.app_id_to_user_id(app_id, user_id) ON DELETE CASCADE -); - -CREATE TABLE emailpassword_user_to_tenant ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - email VARCHAR(256) NOT NULL, - CONSTRAINT emailpassword_user_to_tenant_email_key UNIQUE (app_id, tenant_id, email), - CONSTRAINT emailpassword_user_to_tenant_pkey PRIMARY KEY (app_id, tenant_id, user_id), - CONSTRAINT emailpassword_user_to_tenant_user_id_fkey FOREIGN KEY (app_id, tenant_id, user_id) REFERENCES public.all_auth_recipe_users(app_id, tenant_id, user_id) ON DELETE CASCADE -); - -CREATE TABLE emailpassword_pswd_reset_tokens ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - token VARCHAR(128) NOT NULL, - token_expiry BIGINT NOT NULL, - email VARCHAR(256), - CONSTRAINT emailpassword_pswd_reset_tokens_pkey PRIMARY KEY (app_id, user_id, token), - CONSTRAINT emailpassword_pswd_reset_tokens_token_key UNIQUE (token), - CONSTRAINT emailpassword_pswd_reset_tokens_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.app_id_to_user_id(app_id, user_id) ON UPDATE CASCADE ON DELETE CASCADE -); - -CREATE INDEX emailpassword_password_reset_token_expiry_index ON emailpassword_pswd_reset_tokens (token_expiry); - -CREATE INDEX emailpassword_pswd_reset_tokens_user_id_index ON emailpassword_pswd_reset_tokens (app_id, user_id); - -CREATE TABLE emailverification_verified_emails ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - email VARCHAR(256) NOT NULL, - CONSTRAINT emailverification_verified_emails_pkey PRIMARY KEY (app_id, user_id, email), - CONSTRAINT emailverification_verified_emails_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX emailverification_verified_emails_app_id_index ON emailverification_verified_emails (app_id); - -CREATE TABLE emailverification_tokens ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - email VARCHAR(256) NOT NULL, - token VARCHAR(128) NOT NULL, - token_expiry BIGINT NOT NULL, - CONSTRAINT emailverification_tokens_pkey PRIMARY KEY (app_id, tenant_id, user_id, email, token), - CONSTRAINT emailverification_tokens_token_key UNIQUE (token), - CONSTRAINT emailverification_tokens_tenant_id_fkey FOREIGN KEY (app_id, tenant_id) REFERENCES public.tenants(app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX emailverification_tokens_index ON emailverification_tokens (token_expiry); - -CREATE INDEX emailverification_tokens_tenant_id_index ON emailverification_tokens (app_id, tenant_id); - -CREATE TABLE thirdparty_users ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - third_party_id VARCHAR(28) NOT NULL, - third_party_user_id VARCHAR(256) NOT NULL, - user_id character(36) NOT NULL, - email VARCHAR(256) NOT NULL, - time_joined BIGINT NOT NULL, - CONSTRAINT thirdparty_users_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT thirdparty_users_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.app_id_to_user_id(app_id, user_id) ON DELETE CASCADE -); - -CREATE INDEX thirdparty_users_email_index ON thirdparty_users (app_id, email); - -CREATE INDEX thirdparty_users_thirdparty_user_id_index ON thirdparty_users (app_id, third_party_id, third_party_user_id); - -CREATE TABLE thirdparty_user_to_tenant ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - third_party_id VARCHAR(28) NOT NULL, - third_party_user_id VARCHAR(256) NOT NULL, - CONSTRAINT thirdparty_user_to_tenant_pkey PRIMARY KEY (app_id, tenant_id, user_id), - CONSTRAINT thirdparty_user_to_tenant_third_party_user_id_key UNIQUE (app_id, tenant_id, third_party_id, third_party_user_id), - CONSTRAINT thirdparty_user_to_tenant_user_id_fkey FOREIGN KEY (app_id, tenant_id, user_id) REFERENCES public.all_auth_recipe_users(app_id, tenant_id, user_id) ON DELETE CASCADE -); - -CREATE TABLE passwordless_users ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - email VARCHAR(256), - phone_number VARCHAR(256), - time_joined BIGINT NOT NULL, - CONSTRAINT passwordless_users_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT passwordless_users_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.app_id_to_user_id(app_id, user_id) ON DELETE CASCADE -); - -CREATE TABLE passwordless_user_to_tenant ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - email VARCHAR(256), - phone_number VARCHAR(256), - CONSTRAINT passwordless_user_to_tenant_email_key UNIQUE (app_id, tenant_id, email), - CONSTRAINT passwordless_user_to_tenant_phone_number_key UNIQUE (app_id, tenant_id, phone_number), - CONSTRAINT passwordless_user_to_tenant_pkey PRIMARY KEY (app_id, tenant_id, user_id), - CONSTRAINT passwordless_user_to_tenant_user_id_fkey FOREIGN KEY (app_id, tenant_id, user_id) REFERENCES public.all_auth_recipe_users(app_id, tenant_id, user_id) ON DELETE CASCADE -); - -CREATE TABLE passwordless_devices ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - device_id_hash character(44) NOT NULL, - email VARCHAR(256), - phone_number VARCHAR(256), - link_code_salt character(44) NOT NULL, - failed_attempts integer NOT NULL, - CONSTRAINT passwordless_devices_pkey PRIMARY KEY (app_id, tenant_id, device_id_hash), - CONSTRAINT passwordless_devices_tenant_id_fkey FOREIGN KEY (app_id, tenant_id) REFERENCES public.tenants(app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX passwordless_devices_email_index ON passwordless_devices (app_id, tenant_id, email); - -CREATE INDEX passwordless_devices_phone_number_index ON passwordless_devices (app_id, tenant_id, phone_number); - -CREATE INDEX passwordless_devices_tenant_id_index ON passwordless_devices (app_id, tenant_id); - -CREATE TABLE passwordless_codes ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - code_id character(36) NOT NULL, - device_id_hash character(44) NOT NULL, - link_code_hash character(44) NOT NULL, - created_at BIGINT NOT NULL, - CONSTRAINT passwordless_codes_link_code_hash_key UNIQUE (app_id, tenant_id, link_code_hash), - CONSTRAINT passwordless_codes_pkey PRIMARY KEY (app_id, tenant_id, code_id), - CONSTRAINT passwordless_codes_device_id_hash_fkey FOREIGN KEY (app_id, tenant_id, device_id_hash) REFERENCES public.passwordless_devices(app_id, tenant_id, device_id_hash) ON UPDATE CASCADE ON DELETE CASCADE -); - -CREATE INDEX passwordless_codes_created_at_index ON passwordless_codes (app_id, tenant_id, created_at); - -CREATE INDEX passwordless_codes_device_id_hash_index ON passwordless_codes (app_id, tenant_id, device_id_hash); - -CREATE TABLE jwt_signing_keys ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - key_id VARCHAR(255) NOT NULL, - key_string TEXT NOT NULL, - algorithm VARCHAR(10) NOT NULL, - created_at BIGINT, - CONSTRAINT jwt_signing_keys_pkey PRIMARY KEY (app_id, key_id), - CONSTRAINT jwt_signing_keys_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX jwt_signing_keys_app_id_index ON jwt_signing_keys (app_id); - -CREATE TABLE user_metadata ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - user_metadata TEXT NOT NULL, - CONSTRAINT user_metadata_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT user_metadata_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX user_metadata_app_id_index ON user_metadata (app_id); - -CREATE TABLE roles ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - role VARCHAR(255) NOT NULL, - CONSTRAINT roles_pkey PRIMARY KEY (app_id, role), - CONSTRAINT roles_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX roles_app_id_index ON roles (app_id); - -CREATE TABLE role_permissions ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - role VARCHAR(255) NOT NULL, - permission VARCHAR(255) NOT NULL, - CONSTRAINT role_permissions_pkey PRIMARY KEY (app_id, role, permission), - CONSTRAINT role_permissions_role_fkey FOREIGN KEY (app_id, role) REFERENCES public.roles(app_id, role) ON DELETE CASCADE -); - -CREATE INDEX role_permissions_permission_index ON role_permissions (app_id, permission); - -CREATE INDEX role_permissions_role_index ON role_permissions (app_id, role); - -CREATE TABLE user_roles ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - role VARCHAR(255) NOT NULL, - CONSTRAINT user_roles_pkey PRIMARY KEY (app_id, tenant_id, user_id, role), - CONSTRAINT user_roles_tenant_id_fkey FOREIGN KEY (app_id, tenant_id) REFERENCES public.tenants(app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX user_roles_role_index ON user_roles (app_id, tenant_id, role); - -CREATE INDEX user_roles_tenant_id_index ON user_roles (app_id, tenant_id); - -CREATE INDEX user_roles_app_id_role_index ON user_roles (app_id, role); - -CREATE TABLE totp_users ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - CONSTRAINT totp_users_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT totp_users_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX totp_users_app_id_index ON totp_users (app_id); - -CREATE TABLE totp_user_devices ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - device_name VARCHAR(256) NOT NULL, - secret_key VARCHAR(256) NOT NULL, - period integer NOT NULL, - skew integer NOT NULL, - verified BOOLEAN NOT NULL, - created_at BIGINT, - CONSTRAINT totp_user_devices_pkey PRIMARY KEY (app_id, user_id, device_name), - CONSTRAINT totp_user_devices_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.totp_users(app_id, user_id) ON DELETE CASCADE -); - -CREATE INDEX totp_user_devices_user_id_index ON totp_user_devices (app_id, user_id); - -CREATE TABLE totp_used_codes ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - code VARCHAR(8) NOT NULL, - is_valid BOOLEAN NOT NULL, - expiry_time_ms BIGINT NOT NULL, - created_time_ms BIGINT NOT NULL, - CONSTRAINT totp_used_codes_pkey PRIMARY KEY (app_id, tenant_id, user_id, created_time_ms), - CONSTRAINT totp_used_codes_tenant_id_fkey FOREIGN KEY (app_id, tenant_id) REFERENCES public.tenants(app_id, tenant_id) ON DELETE CASCADE, - CONSTRAINT totp_used_codes_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.totp_users(app_id, user_id) ON DELETE CASCADE -); - -CREATE INDEX totp_used_codes_expiry_time_ms_index ON totp_used_codes (app_id, tenant_id, expiry_time_ms); - -CREATE INDEX totp_used_codes_tenant_id_index ON totp_used_codes (app_id, tenant_id); - -CREATE INDEX totp_used_codes_user_id_index ON totp_used_codes (app_id, user_id); -``` - -:::tip -You also have the option to [rename these tables](./rename-database-tables). -::: - -## 4) Test the connection 🤞 -To test, start SuperTokens and run the following query in your database -```sql -SELECT * FROM key_value; -``` -If you see at least one row, it means that the connection has been successfully completed! 🥳🎉 - -:::tip blog -We also have [a blog post writeup](https://supertokens.com/blog/connect-supertokens-to-database) highlighting all the steps in more detail for different scenarios. -::: diff --git a/v2/thirdpartyemailpassword/custom-ui/init/database-setup/rename-database-tables.mdx b/v2/thirdpartyemailpassword/custom-ui/init/database-setup/rename-database-tables.mdx index 93f84ccc6..e02f73ba3 100644 --- a/v2/thirdpartyemailpassword/custom-ui/init/database-setup/rename-database-tables.mdx +++ b/v2/thirdpartyemailpassword/custom-ui/init/database-setup/rename-database-tables.mdx @@ -4,95 +4,8 @@ title: Rename database tables hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import SelfHostedTabs from "/src/components/tabs/SelfHostedTabs" -import TabItem from '@theme/TabItem'; -# Rename database tables + -:::caution -If you already have tables created by SuperTokens, and then you rename them, SuperTokens will create new tables. So please be sure to migrate the data from the existing one to the new one. -::: - -You can add a prefix to all table names that are managed by SuperTokens. This way, all of them will be renamed in a way that have no clashes with your tables. - -For example, two tables created by SuperTokens are called `emailpassword_users` and `thirdparty_users`. If you add a prefix to them (something like `"my_prefix"`), then the tables will be renamed to `my_prefix_emailpassword_users` and `my_prefix_thirdparty_users`. - -## For MySQL - - - - -```bash -docker run \ - -p 3567:3567 \ - // highlight-next-line - -e MYSQL_TABLE_NAMES_PREFIX="my_prefix" \ - -d registry.supertokens.io/supertokens/supertokens-mysql -``` - - - - -```yaml -# You need to add the following to the config.yaml file. -# The file path can be found by running the "supertokens --help" command - -mysql_table_names_prefix: "my_prefix" -``` - - - -## For PostgreSQL - - - - -```bash -docker run \ - -p 3567:3567 \ - // highlight-next-line - -e POSTGRESQL_TABLE_NAMES_PREFIX="my_prefix" \ - -d registry.supertokens.io/supertokens/supertokens-postgresql -``` - - - - -```yaml -# You need to add the following to the config.yaml file. -# The file path can be found by running the "supertokens --help" command - -postgresql_table_names_prefix: "my_prefix" -``` - - - -## For MongoDB - - - - -```bash -docker run \ - -p 3567:3567 \ - // highlight-next-line - -e MONGODB_COLLECTION_NAMES_PREFIX="my_prefix" \ - -d registry.supertokens.io/supertokens/supertokens-mongodb -``` - - - - - -```yaml -# You need to add the following to the config.yaml file. -# The file path can be found by running the "supertokens --help" command - -mongodb_collection_names_prefix: "my_prefix" -``` - - - diff --git a/v2/thirdpartyemailpassword/custom-ui/init/frontend.mdx b/v2/thirdpartyemailpassword/custom-ui/init/frontend.mdx index 43c0b5bab..bac6ce88b 100644 --- a/v2/thirdpartyemailpassword/custom-ui/init/frontend.mdx +++ b/v2/thirdpartyemailpassword/custom-ui/init/frontend.mdx @@ -4,320 +4,8 @@ title: "Step 1: Frontend" hide_title: true --- +import Redirector from '/src/components/Redirector'; -import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" -import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" -import TabItem from '@theme/TabItem'; -import {Question, Answer}from "/src/components/question" -import AppInfoForm from "/src/components/appInfoForm" -import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" -import WebJsInjector from "/src/components/webJsInjector" -# Frontend Integration + -## Supported frameworks - -import TechStackSupport from "../../../community/reusableMD/supported-tech-stacks-frontend-custom-ui.mdx" - - - -## 1) Install - - - - - - - -```bash -npm i -s supertokens-web-js -``` - - - - -You need to add all of the following scripts to your app - - - -```bash - - - - - -``` - - - - - - - - - - -:::info - -If you want to implement a common authencation experience for both web and mobile, please look at our [**Unified Login guide**](/docs/unified-login/introduction). - -::: - - - - - -```bash -npm i -s supertokens-react-native -# IMPORTANT: If you already have @react-native-async-storage/async-storage as a dependency, make sure the version is 1.12.1 or higher -npm i -s @react-native-async-storage/async-storage -``` - - - - - -Add to your `settings.gradle`: -```bash -dependencyResolutionManagement { - ... - repositories { - ... - maven { url 'https://jitpack.io' } - } -} -``` - -Add the following to you app level's `build.gradle`: -```bash -implementation 'com.github.supertokens:supertokens-android:X.Y.Z' -``` - -You can find the latest version of the SDK [here](https://github.com/supertokens/supertokens-android/releases) (ignore the `v` prefix in the releases). - - - - - -#### Using Cocoapods - -Add the Cocoapod dependency to your Podfile - -```bash -pod 'SuperTokensIOS' -``` - -#### Using Swift Package Manager - -Follow the [official documentation](https://developer.apple.com/documentation/xcode/adding-package-dependencies-to-your-app) to learn how to use Swift Package Manager to add dependencies to your project. - -When adding the dependency use the `master` branch after you enter the supertokens-ios repository URL: - -```bash -https://github.com/supertokens/supertokens-ios -``` - - - - - -Add the dependency to your pubspec.yaml - -```bash -supertokens_flutter: ^X.Y.Z -``` - -You can find the latest version of the SDK [here](https://github.com/supertokens/supertokens-flutter/releases) (ignore the `v` prefix in the releases). - - - - - - - - - -## 2) Call the `init` function - - - - - - - - - -Call the following `init` function at the start of your app (ideally on the global scope). - -```tsx -import SuperTokens from 'supertokens-web-js'; -import Session from 'supertokens-web-js/recipe/session'; -import ThirdParty from 'supertokens-web-js/recipe/thirdparty' -import EmailPassword from 'supertokens-web-js/recipe/emailpassword' - -SuperTokens.init({ - appInfo: { - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}", - appName: "...", - }, - recipeList: [ - Session.init(), - EmailPassword.init(), - ThirdParty.init() - ], -}); -``` - - - - - - - - -Call the following `init` function at the start of your app (ideally on the global scope). - -```tsx -import supertokens from "supertokens-web-js-script"; -import supertokensSession from "supertokens-web-js-script/recipe/session"; -import supertokensThirdParty from 'supertokens-web-js-script/recipe/thirdparty' -import supertokensEmailPassword from 'supertokens-web-js-script/recipe/emailpassword' -supertokens.init({ - appInfo: { - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}", - appName: "...", - }, - recipeList: [ - supertokensSession.init(), - supertokensThirdParty.init(), - supertokensEmailPassword.init() - ], -}); -``` - - - - - - - - - - - - - - - - -Call the following `init` function at the start of your app (ideally on the global scope). - -```tsx -import SuperTokens from 'supertokens-react-native'; - -SuperTokens.init({ - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}", -}); -``` - - - - - - - - - -Add the `SuperTokens.init` function call at the start of your application. - -```kotlin -import android.app.Application -import com.supertokens.session.SuperTokens - -class MainApplication: Application() { - override fun onCreate() { - super.onCreate() - - SuperTokens.Builder(this, "^{form_apiDomain}") - .apiBasePath("^{form_apiBasePath}") - .build() - } -} -``` - - - - - - - - - -Add the `SuperTokens.initialize` function call at the start of your application. - -```swift -import UIKit -import SuperTokensIOS - -fileprivate class ApplicationDelegate: UIResponder, UIApplicationDelegate { - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - do { - try SuperTokens.initialize( - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}" - ) - } catch SuperTokensError.initError(let message) { - // TODO: Handle initialization error - } catch { - // Some other error - } - - return true - } - -} -``` - - - - - - - - - -Add the `SuperTokens.init` function call at the start of your application. - -```dart -import 'package:supertokens_flutter/supertokens.dart'; - -void main() { - SuperTokens.init( - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}", - ); -} -``` - - - - - - - - - - - -## What to do next? -The above code snippet sets up session management network interceptors on the frontend. Our frontend SDK will now be able to automatically save and add session tokens to each request to your API layer and also do auto session refreshing. - -The next steps are to: -- Step 2: setup the backend SDK in your API layer -- Step 3: Setup the SuperTokens core (sign up for managed service, or self host it) -- Step 4: Enable the user management dashboard -- Use the frontend SDK's helper functions to build your own UI - follow along the docs in the "Using your own UI" section and you will find docs for this after "Step 4". diff --git a/v2/thirdpartyemailpassword/custom-ui/init/user-management-dashboard/managing-user-roles-and-permissions.mdx b/v2/thirdpartyemailpassword/custom-ui/init/user-management-dashboard/managing-user-roles-and-permissions.mdx index ec916c7a2..ae431bb4c 100644 --- a/v2/thirdpartyemailpassword/custom-ui/init/user-management-dashboard/managing-user-roles-and-permissions.mdx +++ b/v2/thirdpartyemailpassword/custom-ui/init/user-management-dashboard/managing-user-roles-and-permissions.mdx @@ -4,157 +4,7 @@ title: "Managing user roles and permissions" hide_title: true --- +import Redirector from '/src/components/Redirector'; - - - + -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import TabItem from '@theme/TabItem'; - -# Managing user roles and permissions - -You can manage [user roles and permissions](/docs/userroles/introduction) of your app from the user management dashboard. - -## Initialisation - -To begin configuring user roles and permissions on the user management dashboard, start by initializing the "UserRoles" recipe in the `recipeList` on the backend. - - - - -```tsx -import SuperTokens from "supertokens-node"; -import Dashboard from "supertokens-node/recipe/dashboard"; -import UserRoles from "supertokens-node/recipe/userroles" - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // TODO: Initialise other recipes if needed. - Dashboard.init(), - // highlight-start - UserRoles.init() - // highlight-end - ], -}); -``` -:::important Note - -Please note that the capability to manage roles and permissions from the user management dashboard is available only from Node SDK version `v16.6.0` onwards. - -::: - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/dashboard" - "github.com/supertokens/supertokens-golang/recipe/userroles" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - supertokens.Init(supertokens.TypeInput{ - RecipeList: []supertokens.Recipe{ - // TODO: Initialise other recipes if needed - dashboard.Init(nil), - // highlight-start - userroles.Init(nil), - // highlight-end - }, - }); -} -``` - - - - - -```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import dashboard, userroles - -init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore - recipe_list=[ - # TODO: Initialise other recipes if needed. - dashboard.init(), - # highlight-start - userroles.init() - # highlight-end - ] -) -``` - - - - - - - -## Managing roles and permissions - -When you first use the `UserRoles` recipe, the list of roles will be empty. To create roles, simply click on the "Add Role" button. - -No roles created - -This action will open a modal, enabling you to create a role along with its associated permissions. Permissions are essentially a list of strings assigned to a specific role. - -Create role - -After creating a role, the UI should display a list of all roles in your app. - -Roles list - -You can now preview the role you created by clicking on the role row. The modal provides options to edit or delete the role. - -Preview role - -## Manging roles and users - -To assign a specific role to a user, start by finding the user in the dashboard. Upon clicking the user, navigate to the user details page where you'll find a section for user roles. - -If the selected user is associated with multiple tenants, you can choose a 'tenantId' from the dropdown menu to specify the tenant for which you'd like to assign roles. - -Select tenant - -Click the edit button to start assigning roles. Then, select the "Assign Role" button, and a modal will appear with a list of available roles for assignment to this user. - -Assign role - -To remove a role assigned to a user, simply click on the "X" icon next to that specific role. - -View assigned role - - - - -:::important - -Our Python SDK doesn't currently support roles management via the user management UI. Instead, you can use the functions exposed by the SDK to manage roles and permissions. Check out the guide [here](/docs/userroles/introduction). - -::: - - - - - -:::important - -Our Golang SDK doesn't currently support roles management via the user management UI. Instead, you can use the functions exposed by the SDK to manage roles and permissions. Check out the guide [here](/docs/userroles/introduction). - -::: - - - - - - - diff --git a/v2/thirdpartyemailpassword/custom-ui/init/user-management-dashboard/setup.mdx b/v2/thirdpartyemailpassword/custom-ui/init/user-management-dashboard/setup.mdx index 0c108af83..ce23dfa60 100644 --- a/v2/thirdpartyemailpassword/custom-ui/init/user-management-dashboard/setup.mdx +++ b/v2/thirdpartyemailpassword/custom-ui/init/user-management-dashboard/setup.mdx @@ -4,329 +4,7 @@ title: "Setting up the dashboard" hide_title: true --- -# Setting up the Dashboard +import Redirector from '/src/components/Redirector'; - - - + -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import AppInfoForm from "/src/components/appInfoForm" -import CustomAdmonition from "/src/components/customAdmonition" - - -## Architecture - - - -Flowchart of architecture when using SuperTokens managed service - - -Flowchart of architecture when self-hosting SuperTokens - - - -The Backend SDK serves the user management dashboard which can be accessed on the `/auth/dashboard` path of your API domain. - - -## Initialise the dashboard recipe - - - -To get started, initialise the Dashboard recipe in the `recipeList`. - - - - -```tsx -import SuperTokens from "supertokens-node"; -import Dashboard from "supertokens-node/recipe/dashboard"; - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // TODO: Initialise other recipes - // highlight-start - Dashboard.init(), - // highlight-end - ], -}); -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/dashboard" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - supertokens.Init(supertokens.TypeInput{ - RecipeList: []supertokens.Recipe{ - // TODO: Initialise other recipes - // highlight-start - dashboard.Init(nil), - // highlight-end - }, - }); -} -``` - - - - -```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import dashboard - -init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore - recipe_list=[ - # TODO: Initialise other recipes - # highlight-start - dashboard.init(), - # highlight-end - ] -) -``` - - - - -## Viewing the dashboard - -:::important -The user management dashboard is served by the backend SDK, you have to use your API domain when trying to visit the dashboard. -::: - -Navigate to `^{form_apiDomain}^{form_apiBasePath}/dashboard` to view the dashboard. - -:::note -If you are using Next.js, upon integrating our backend SDK into your Next.js API folder, the dashboard will be accessible by default at `^{form_apiDomain}/api/auth/dashboard`. For frameworks other than Next.js, it can be accessed at `^{form_apiDomain}/auth/dashboard`. Should you have customized the `apiBasePath` configuration property, simply navigate to `^{form_apiDomain}^{form_apiBasePath}/dashboard` to access the dashboard. -::: - -Dashboard login screen UI - -## Creating dashboard credentials - - - -You can create 3 dashboard users* for free. - -If you need to create additional users: - -- For self hosted users, please [sign up](https://supertokens.com/auth) to generate a license key and follow the instructions sent to you by email. -- For managed service users, you can click on the "enable paid features" button on [our dashboard](https://supertokens.com/dashboard-saas), and follow the steps from there on. - -*: A dashboard user is a user that can log into and view the user management dashboard. These users are independent to the users of your application - - - -When you first setup SuperTokens, there are no credentials created for the dashboard. If you click the "Add a new user" button in the dashboard login screen you can see the command you need to execute in order to create credentials. - -Dashboard signup screen UI - -To create credentials you need to make a request to SuperTokens core. - -- The example above uses the demo core `https://try.supertokens.com`, replace this with the connection uri you pass to the backend SDK when initialising SuperTokens. -- Replace `` with your API key. If you are using a self hosted SuperTokens core there is no API key by default. In that case you can either skip or ignore the `api-key` header. -- Replace `` and `` with the appropriate values. - -:::caution -If using self hosted SuperTokens core, you need to make sure that you add an API key to the core in case it's exposed to the internet. Otherwise anyone will be able to create or modify dashboard users. - -You can add an API key to the core by following the instructions "Auth flow customizations" > "SuperTokens core settings" > "Adding API keys" page. -::: - -## Updating dashboard credentials - -You can update the email or password of existing credentials by using the "Forgot Password" button on the dashboard login page. - -Reset your password screen UI - -To update credentials you need to make a request to SuperTokens core. - -- The example above uses the demo core `https://try.supertokens.com`, replace this with the connection uri you pass to the backend SDK when initialising SuperTokens. -- Replace `` with your API key. If you are using a self hosted SuperTokens core there is no API key by default. In that case you can either skip or ignore the `api-key` header. -- Replace `` and `` with the appropriate values. You can use `newEmail` instead of `newPassword` if you want to update the email - - - - - -## Restricting access to dashboard users - -When using the dashboard recipe you can restrict access to certain features by providing a list of emails to be considered as "admins". When a dashboard user logs in with an email not present in this list, they will only be able to perform read operations and all write operations will result in the backend SDKs failing the request. - -You can provide an array of emails to the backend SDK when initialising the dashboard recipe: - -:::important -- Not providing an admins array will result in all dashboard users being allowed both read and write operations -- Providing an empty array as admins will result in all dashboard users having ONLY read access -::: - - - - -```tsx -import SuperTokens from "supertokens-node"; -import Dashboard from "supertokens-node/recipe/dashboard"; - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // TODO: Initialise other recipes - // highlight-start - Dashboard.init({ - admins: [ - "johndoe@gmail.com", - ], - }), - // highlight-end - ], -}); -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/dashboard" - "github.com/supertokens/supertokens-golang/supertokens" - "github.com/supertokens/supertokens-golang/recipe/dashboard/dashboardmodels" -) - -func main() { - supertokens.Init(supertokens.TypeInput{ - RecipeList: []supertokens.Recipe{ - // TODO: Initialise other recipes - // highlight-start - dashboard.Init(&dashboardmodels.TypeInput{ - Admins: &[]string{ - "johndoe@gmail.com", - }, - }), - // highlight-end - }, - }); -} -``` - - - - -```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import dashboard - -init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore - recipe_list=[ - # TODO: Initialise other recipes - # highlight-start - dashboard.init( - admins=[ - "johndoe@gmail.com", - ], - ), - # highlight-end - ] -) -``` - - - - -## Content Security Policy - - - - -If your backend returns a `Content-Security-Policy` header, you will encounter the following UI displaying the CSP violation details. Follow the instructions provided in this UI to make necessary adjustments to your backend CSP configuration. - -![CSP error handled UI](/img/dashboard/csp-error.png) - - -For example, to address the error message displayed in the above screenshot, you need to modify your `original policy`. In the given example, it appears as follows: - -```text -script-src: - 'self' - 'unsafe-inline' - https://google.com - -img-src: - https://google.com -``` - -To resolve this issue, make the following adjustments: - - -```text -script-src: - 'self' - 'unsafe-inline' - https://google.com - -img-src: - https://google.com - https://cdn.jsdelivr.net/gh/supertokens/ - -``` -Essentially, you need to include the domain listed as the `Blocked URI` in your violated directive block within your original policy. - - - -If you return a `Content-Security-Policy` header in from your backend, you will need to include the following directives for the user management dashboard to work correctly - -```text -script-src: - 'self' - 'unsafe-inline' - https://cdn.jsdelivr.net/gh/supertokens/ -img-src: - https://cdn.jsdelivr.net/gh/supertokens/ - https://purecatamphetamine.github.io/ -``` - - - - -If you return a `Content-Security-Policy` header in from your backend, you will need to include the following directives for the user management dashboard to work correctly - -```text -script-src: - 'self' - 'unsafe-inline' - https://cdn.jsdelivr.net/gh/supertokens/ -img-src: - https://cdn.jsdelivr.net/gh/supertokens/ - https://purecatamphetamine.github.io/ -``` - - - - - \ No newline at end of file diff --git a/v2/thirdpartyemailpassword/custom-ui/init/user-management-dashboard/tenant-management/details.mdx b/v2/thirdpartyemailpassword/custom-ui/init/user-management-dashboard/tenant-management/details.mdx index 4719a57cf..1397ce110 100644 --- a/v2/thirdpartyemailpassword/custom-ui/init/user-management-dashboard/tenant-management/details.mdx +++ b/v2/thirdpartyemailpassword/custom-ui/init/user-management-dashboard/tenant-management/details.mdx @@ -4,65 +4,7 @@ title: "Tenant Details" hide_title: true --- - - - +import Redirector from '/src/components/Redirector'; -# Tenant details + -Upon selection or creation of a tenant, you will be presented with the Tenant Details page. The various sections are explained below. - -Tenant details - -## Tenant ID and users - -The first section shows up the tenant ID and the number of users in that tenant. Clicking on `See Users` takes you to the [user management page](/docs/userdashboard/users-listing-and-details) where the users for the selected tenant can be viewed and managed. - -Tenant users - - -## Enabled Login Methods - -This section displays the various login methods available for the tenant. By enabling these toggles, you can make the corresponding login methods accessible to the users within the tenant. - -Appropriate recipes must be enabled to be able to turn on the login methods. For example, - -- to be able to turn on `emailpassword`, EmailPassword recipe must be initialised in the backend. -- to be able to turn on `OTP Phone`, Passwordless recipe must be initialised with flowType `USER_INPUT_CODE` and contactMethod `PHONE` - -:::info - -If you are using our Auth React SDK, make sure to enable [usesDynamicLoginMethods](/docs/passwordless/common-customizations/multi-tenancy/common-domain-login#step-3-tell-supertokens-about-the-saved-tenantid-from-the-previous-step) so that the frontend can automatically show the login methods based on the selection here. - -::: - -Login Methods - -## Secondary factors - -This section displays the various secondary factors available for the tenant. By enabling these toggles, the corresponding factor will be enabled for all users of the tenant. Refer [Multifactor Authentication docs](/docs/mfa/introduction) for more information. - -[MultiFactorAuth](/docs/mfa/backend-setup) recipe must be initialised to be able to enable Secondary Factors. - -Also, appropriate recipes must be initialised in the backend SDK to be able to use a secondary factor. For example, - -- to be able to turn on TOTP, TOTP recipe must be initialised in the backend. -- to be able to turn on `OTP Phone`, Passwordless recipe must be initialised with flowType `USER_INPUT_CODE` and contactMethod `PHONE` - -Secondary Factors - -## Core config - -Core Config - -This section shows the current config values in core for the tenant. You can edit some of these settings by clicking the `pencil` icon next to the property. - -Edit Core Config - -:::caution - -Some of the config values may not be editable since they are being inherited from the App. If using Supertokens managed hosting, they can be modified in the SaaS Dashboard. Else, if you are self-hosting the SuperTokens core, they will have to be edited via Docker env variables or the config.yaml file. - -::: - - diff --git a/v2/thirdpartyemailpassword/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx b/v2/thirdpartyemailpassword/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx index 32c5447bd..6ae231e40 100644 --- a/v2/thirdpartyemailpassword/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx +++ b/v2/thirdpartyemailpassword/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx @@ -4,29 +4,7 @@ title: "Overview" hide_title: true --- - - - +import Redirector from '/src/components/Redirector'; -# Tenant management overview + -You can now manage [tenants, login methods and third party providers](/docs/multitenancy/introduction) and [Multi factor authentication](/docs/mfa/introduction) of your app from the tenant management dashboard. - -Once the dashboard recipe is initialised, the tenant management should be available without requiring any additional steps. - -:::caution - -Currently, this is only available with our Node and Python SDKs. - -::: - -Tenant Management Landing - - -## Creating a new tenant - -Clicking on `Add Tenant` will prompt you to enter the tenant id. Once the tenant id is entered, click on `Create Now` to create the tenant. You will then be taken to the Tenant Details page where you can further manage the newly created tenant. - -Create Tenant - - diff --git a/v2/thirdpartyemailpassword/custom-ui/init/user-management-dashboard/users-listing-and-details.mdx b/v2/thirdpartyemailpassword/custom-ui/init/user-management-dashboard/users-listing-and-details.mdx index ff5945ed2..7be11467c 100644 --- a/v2/thirdpartyemailpassword/custom-ui/init/user-management-dashboard/users-listing-and-details.mdx +++ b/v2/thirdpartyemailpassword/custom-ui/init/user-management-dashboard/users-listing-and-details.mdx @@ -4,38 +4,7 @@ title: "Viewing user list and details" hide_title: true --- - - - +import Redirector from '/src/components/Redirector'; -# Viewing user list and details + -With the user management dashboard you can view the list of users and their details. You can also perform different operations on theses users as mentioned below. - -## Viewing users list - -If you have just created your app, you may not have any users to show on the dashboard. - -Empty dashboard screen UI - -Navigate to the your frontend app and create a user (via the sign up flow). On creation, if you head back to the dashboard and refresh the page, you will see that user: - -One user in dashboard screen UI - -## User details page - -When you select a user you can view detailed information about the user such as email, phone number, user metadata etc. - -User details page screen UI part one - -User details page screen UI part two - -You can edit user information and perform actions such as resetting a user's password or revoke sessions for a user. - -Change password modal UI - -:::important Note -Some features such as user metadata and email verification have to be enabled in your backend before you can use them in the user management dashboard -::: - - diff --git a/v2/thirdpartyemailpassword/custom-ui/multitenant-login.mdx b/v2/thirdpartyemailpassword/custom-ui/multitenant-login.mdx index 8e1c60b4c..bf2091f6c 100644 --- a/v2/thirdpartyemailpassword/custom-ui/multitenant-login.mdx +++ b/v2/thirdpartyemailpassword/custom-ui/multitenant-login.mdx @@ -4,429 +4,8 @@ title: "Multitenant and Enterprise SSO login" hide_title: true --- -import AppInfoForm from "/src/components/appInfoForm" -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import PythonSyncAsyncSubTabs from "/src/components/tabs/PythonSyncAsyncSubTabs"; -import CoreVersionSubTabs from "/src/components/tabs/CoreVersionSubTabs"; -import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs"; -import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" -import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" -import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" +import Redirector from '/src/components/Redirector'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import MultiTenancyPaidBanner from '../../community/reusableMD/multitenancy/MultiTenancyPaidBanner.mdx' -import CoreInjector from "/src/components/coreInjector" - - - + - - -# Multitenant and Enterprise SSO login - -Multitenant login is a feature that lets you customize the login experience for each of your customers. For example, a customer `customer1` hosted on `customer1.yourdomain.com` can have login with `Active Directory` and `Google`, and another customer `customer2` hosted on `customer2.yourdomain.com` can have login with `Okta`, `Facebook` and email password based login. - -This is also the page that you should see if you want to implement sign in with: -- Okta (`thirdPartyId: "okta"`) -- SAML (`thirdPartyId: "boxy-saml"`) -- Active Directory (`thirdPartyId: "active-directory"`) -- Google Workspaces (`thirdPartyId: "google-workspaces"`) -- GitLab (`thirdPartyId: "gitlab"`) -- Bitbucket (`thirdPartyId: "bitbucket"`) -- Or any other workforce IdP - - - - - - - -## Step 1: Create and configure a new tenant in SuperTokens core - -Each tenant can be configured with a unique `tenantId`, and the list of third party connections (Active Directory, Google etc..) that should be allowed for them. - -You can create a new tenant using our backend SDKs or via a `cURL` command to the core. - - - - - - - - - - - -```tsx -import Multitenancy from "supertokens-node/recipe/multitenancy"; - -async function createNewTenant() { - - // highlight-start - let resp = await Multitenancy.createOrUpdateTenant("customer1", { - firstFactors: ["emailpassword", "thirdparty"] - }); - // highlight-end - - if (resp.createdNew) { - // new tenant was created - } else { - // existing tenant's config was modified. - } -} -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/multitenancy" - "github.com/supertokens/supertokens-golang/recipe/multitenancy/multitenancymodels" -) - -func main() { - tenantId := "customer1" - thirdPartyEnabled := true - emailPasswordEnabled := true - - // highlight-start - resp, err := multitenancy.CreateOrUpdateTenant(tenantId, multitenancymodels.TenantConfig{ - ThirdPartyEnabled: &thirdPartyEnabled, - EmailPasswordEnabled: &emailPasswordEnabled, - }) - // highlight-end - - if err != nil { - // handle error - } - if resp.OK.CreatedNew { - // new tenant was created - } else { - // existing tenant's config was modified. - } -} -``` - - - - - - - -```python -from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate - - -async def update_tenant(): - result = await create_or_update_tenant( - "customer1", - config=TenantConfigCreateOrUpdate( - first_factors=["thirdparty", "emailpassword"], - ), - ) - - if result.status != "OK": - print("Error creating or updating tenant") - elif result.created_new: - print("New tenant was created") - else: - print("Existing tenant's config was modified") -``` - - - - - -```python -from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate - - -def update_tenant(): - result = create_or_update_tenant( - "customer1", - config=TenantConfigCreateOrUpdate( - first_factors=["thirdparty", "emailpassword"], - ), - ) - - if result.status != "OK": - print("Error creating or updating tenant") - elif result.created_new: - print("New tenant was created") - else: - print("Existing tenant's config was modified") -``` - - - - - - - - - - - - - - -```bash -curl --location --request PUT '^{coreInjector_uri_without_quotes}/recipe/multitenancy/tenant/v2' \ ---header 'api-key: ^{coreInjector_api_key_without_quotes}' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "tenantId": "customer1", - "firstFactors": ["emailpassword", "thirdparty"] -}' -``` - - - - - -```bash -curl --location --request PUT '^{coreInjector_uri_without_quotes}/recipe/multitenancy/tenant' \ ---header 'api-key: ^{coreInjector_api_key_without_quotes}' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "tenantId": "customer1", - "emailPasswordEnabled": true, - "thirdPartyEnabled": true -}' -``` - - - - - - - - - - - -Create Tenant - - - - - - - - - - - -Once a tenant is created, add their thirdparty providers as shown below. - - - - - - - - - - - -```tsx -import Multiteancy from "supertokens-node/recipe/multitenancy"; - -async function createTenant() { - let resp = await Multiteancy.createOrUpdateThirdPartyConfig("customer1", { - thirdPartyId: "active-directory", - name: "Active Directory", - clients: [{ - clientId: "...", - clientSecret: "...", - }], - oidcDiscoveryEndpoint: "https://login.microsoftonline.com//v2.0/.well-known/openid-configuration", - }); - - if (resp.createdNew) { - // Active Directory added to customer1 - } else { - // Existing active directory config overwritten for customer1 - } -} -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/multitenancy" - "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" -) - -func main() { - tenantId := "customer1" - - // highlight-start - resp, err := multitenancy.CreateOrUpdateThirdPartyConfig(tenantId, tpmodels.ProviderConfig{ - ThirdPartyId: "active-directory", - Name: "Active Directory", - Clients: []tpmodels.ProviderClientConfig{ - { - ClientID: "...", - ClientSecret: "...", - }, - }, - OIDCDiscoveryEndpoint: "https://login.microsoftonline.com//v2.0/.well-known/openid-configuration", - }, nil, nil) - // highlight-end - - if err != nil { - // handle error - } - if resp.OK.CreatedNew { - // Active Directory added to customer1 - } else { - // Existing active directory config overwritten for customer1 - } -} -``` - - - - - - - -```python -from supertokens_python.recipe.multitenancy.asyncio import create_or_update_third_party_config -from supertokens_python.recipe.thirdparty.provider import ProviderConfig, ProviderClientConfig - -async def update_tenant(): - result = await create_or_update_third_party_config( - "customer1", - config=ProviderConfig( - third_party_id="active-directory", - name="Active Directory", - clients=[ - ProviderClientConfig( - client_id="...", - client_secret="...", - ) - ], - oidc_discovery_endpoint="https://login.microsoftonline.com//v2.0/.well-known/openid-configuration", - ), - ) - - if result.status != "OK": - print("Error adding active directory to tenant") - elif result.created_new: - print("Active directory was added to the tenant") - else: - print("Existing tenant's active directory config was modified") -``` - - - - - -```python -from supertokens_python.recipe.multitenancy.syncio import create_or_update_third_party_config -from supertokens_python.recipe.thirdparty.provider import ProviderConfig, ProviderClientConfig - -result = create_or_update_third_party_config( - "customer1", - config=ProviderConfig( - third_party_id="active-directory", - name="Active Directory", - clients=[ - ProviderClientConfig( - client_id="...", - client_secret="...", - ) - ] - ), - ) - -if result.status != "OK": - print("Error creating or updating tenant") -elif result.created_new: - print("New tenant was created") -else: - print("Existing tenant's config was modified") -``` - - - - - - - - - - -```bash -curl --location --request PUT '^{coreInjector_uri_without_quotes}//recipe/multitenancy/config/thirdparty' \ ---header 'api-key: ^{coreInjector_api_key_without_quotes}' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "config": { - "thirdPartyId": "active-directory", - "name": "Active Directory", - "clients": [ - { - "clientId": "...", - "clientSecret": "..." - } - ], - "oidcDiscoveryEndpoint": "https://login.microsoftonline.com//v2.0/.well-known/openid-configuration" - } -}' -``` - - - - - - - -Create Third Party Provider - - - - - - - - - - - -:::important -The above shows how to add an Active Directory config for the `customer1` tenant. You can see the config structure for all the in built providers [on this page](../common-customizations/sign-in-and-up/provider-config). -::: - - - - - - - -## Step 2: Build your multi tenant a UX flow - -The most common multi tenant flows are: -- [Tenants use a common domain to login](../common-customizations/multi-tenancy/common-domain-login): All tenants login using the same page (for example, `example.com/auth`) and are optionally redirected to their sub domain post login. At the start of the login flow, the customer will have to input their tenantId / workspace URL / identifier - as defined by you, and the login methods shown would be based on their tenantId. -- [Tenants use their sub domain to login](../common-customizations/multi-tenancy/sub-domain-login): Here, each tenant has a sub domain assigned to them (for example `customer1.example.com`, `customer2.example.com`, ...), and they would visit their sub domain to login and access their app. Each sub domain's login experience may be different (as defined by you or the tenant). - -SuperTokens is flexible enough to allow other forms of UX as well, but since the above two flow are most common, we provide dedicated docs for them (see the links above). - -## See also - -- Multi tenant [single domain](../common-customizations/multi-tenancy/common-domain-login) or [sub domain](../common-customizations/multi-tenancy/sub-domain-login) login. -- [See all built in multitenant providers](../common-customizations/sign-in-and-up/provider-config). -- [See how to add custom multitenant provider](../common-customizations/multi-tenancy/custom-provider). -- [SAML login](../common-customizations/saml/what-is-saml). -- [Tenant data isolation](../common-customizations/multi-tenancy/new-tenant-config#providing-additional-configuration-per-tenant). - - diff --git a/v2/thirdpartyemailpassword/custom-ui/securing-routes.mdx b/v2/thirdpartyemailpassword/custom-ui/securing-routes.mdx index 665ebb50b..adf124a95 100644 --- a/v2/thirdpartyemailpassword/custom-ui/securing-routes.mdx +++ b/v2/thirdpartyemailpassword/custom-ui/securing-routes.mdx @@ -4,590 +4,8 @@ title: "Securing your API and frontend routes" hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -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" -import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" -import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" -import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" + -# Securing your API and frontend routes - -## Protecting APIs - - - - - - - -### Requiring an active session - -For your APIs that require a user to be logged in, use the `verifySession` middleware - - - - - - -```tsx -import express from "express"; -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import { SessionRequest } from "supertokens-node/framework/express"; - -let app = express(); - -// highlight-start -app.post("/like-comment", verifySession(), (req: SessionRequest, res) => { - let userId = req.session!.getUserId(); - // highlight-end - //.... -}); -``` - - - - -```tsx -import Hapi from "@hapi/hapi"; -import { verifySession } from "supertokens-node/recipe/session/framework/hapi"; -import { SessionRequest } from "supertokens-node/framework/hapi"; - -let server = Hapi.server({ port: 8000 }); - -server.route({ - path: "/like-comment", - method: "post", - //highlight-start - options: { - pre: [ - { - method: verifySession() - }, - ], - }, - handler: async (req: SessionRequest, res) => { - let userId = req.session!.getUserId(); - //highlight-end - //... - } -}) -``` - - - -```tsx -import Fastify from "fastify"; -import { verifySession } from "supertokens-node/recipe/session/framework/fastify"; -import { SessionRequest } from "supertokens-node/framework/fastify"; - -let fastify = Fastify(); - -//highlight-start -fastify.post("/like-comment", { - preHandler: verifySession(), -}, (req: SessionRequest, res) => { - let userId = req.session!.getUserId(); - //highlight-end - //.... -}); -``` - - - - -```tsx -import { verifySession } from "supertokens-node/recipe/session/framework/awsLambda"; -import { SessionEventV2 } from "supertokens-node/framework/awsLambda"; - -async function likeComment(awsEvent: SessionEventV2) { - let userId = awsEvent.session!.getUserId(); - //.... -}; - -//highlight-next-line -exports.handler = verifySession(likeComment); -``` - - - - -```tsx -import KoaRouter from "koa-router"; -import { verifySession } from "supertokens-node/recipe/session/framework/koa"; -import { SessionContext } from "supertokens-node/framework/koa"; - -let router = new KoaRouter(); - -//highlight-start -router.post("/like-comment", verifySession(), (ctx: SessionContext, next) => { - let userId = ctx.session!.getUserId(); - //highlight-end - //.... -}); -``` - - - - -```tsx -import { inject, intercept } from "@loopback/core"; -import { RestBindings, MiddlewareContext, post, response } from "@loopback/rest"; -import { verifySession } from "supertokens-node/recipe/session/framework/loopback"; -import { SessionContext } from "supertokens-node/framework/loopback"; - -class LikeComment { - //highlight-start - constructor(@inject(RestBindings.Http.CONTEXT) private ctx: MiddlewareContext) { } - @post("/like-comment") - @intercept(verifySession()) - @response(200) - handler() { - let userId = (this.ctx as SessionContext).session!.getUserId(); - //highlight-end - //.... - } -} -``` - - - - -```tsx -import { superTokensNextWrapper } from 'supertokens-node/nextjs' -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import { SessionRequest } from "supertokens-node/framework/express"; - -// highlight-start -export default async function likeComment(req: SessionRequest, res: any) { - await superTokensNextWrapper( - async (next) => { - await verifySession()(req, res, next); - }, - req, - res - ) - - let userId = req.session!.getUserId(); - // highlight-end - //.... -} -``` - - - - -```tsx -import { NextResponse, NextRequest } from "next/server"; -import SuperTokens from "supertokens-node"; -import { withSession } from "supertokens-node/nextjs"; -// @ts-ignore -import { backendConfig } from "@/app/config/backend"; - -SuperTokens.init(backendConfig()); - -export function POST(request: NextRequest) { - return withSession(request, async (err, session) => { - if (err) { - return NextResponse.json(err, { status: 500 }); - } - const userId = session!.getUserId(); - //.... - return NextResponse.json({}); - }); -} -``` - - - - -```tsx -import { Controller, Post, UseGuards, Session } from "@nestjs/common"; -import { SessionContainer } from "supertokens-node/recipe/session"; -// @ts-ignore -import { AuthGuard } from './auth/auth.guard'; - -@Controller() -export class ExampleController { - @Post('example') - @UseGuards(new AuthGuard()) // For more information about this guard please read our NestJS guide. - async postExample(@Session() session: SessionContainer): Promise { - //highlight-start - let userId = session.getUserId(); - - //highlight-end - //.... - return true; - } -} -``` - - - - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/supertokens/supertokens-golang/recipe/session" -) - -func main() { - _ = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - // Wrap the API handler in session.VerifySession - session.VerifySession(nil, likeCommentAPI).ServeHTTP(rw, r) - }) -} - -func likeCommentAPI(w http.ResponseWriter, r *http.Request) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(r.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/gin-gonic/gin" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" -) - -func main() { - router := gin.New() - - // Wrap the API handler in session.VerifySession - router.POST("/likecomment", verifySession(nil), likeCommentAPI) -} - -// This is a function that wraps the supertokens verification function -// to work the gin -func verifySession(options *sessmodels.VerifySessionOptions) gin.HandlerFunc { - return func(c *gin.Context) { - session.VerifySession(options, func(rw http.ResponseWriter, r *http.Request) { - c.Request = c.Request.WithContext(r.Context()) - c.Next() - })(c.Writer, c.Request) - // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly - c.Abort() - } -} - -func likeCommentAPI(c *gin.Context) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(c.Request.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/go-chi/chi" - "github.com/supertokens/supertokens-golang/recipe/session" -) - -func main() { - r := chi.NewRouter() - - // Wrap the API handler in session.VerifySession - r.Post("/likecomment", session.VerifySession(nil, likeCommentAPI)) -} - -func likeCommentAPI(w http.ResponseWriter, r *http.Request) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(r.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/gorilla/mux" - "github.com/supertokens/supertokens-golang/recipe/session" -) - -func main() { - router := mux.NewRouter() - - // Wrap the API handler in session.VerifySession - router.HandleFunc("/likecomment", session.VerifySession(nil, likeCommentAPI)).Methods(http.MethodPost) -} - -func likeCommentAPI(w http.ResponseWriter, r *http.Request) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(r.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - - - - - - -```python -from supertokens_python.recipe.session.framework.fastapi import verify_session -from supertokens_python.recipe.session import SessionContainer -from fastapi import Depends - -# highlight-start -@app.post('/like_comment') # type: ignore -async def like_comment(session: SessionContainer = Depends(verify_session())): - user_id = session.get_user_id() - # highlight-end - - print(user_id) -``` - - - - -```python -from supertokens_python.recipe.session.framework.flask import verify_session -from supertokens_python.recipe.session import SessionContainer -from flask import g - -# highlight-start -@app.route('/update-jwt', methods=['POST']) # type: ignore -@verify_session() -def like_comment(): - session: SessionContainer = g.supertokens # type: ignore - - user_id = session.get_user_id() - # highlight-end - - print(user_id) -``` - - - - -```python -from supertokens_python.recipe.session.framework.django.asyncio import verify_session -from django.http import HttpRequest -from supertokens_python.recipe.session import SessionContainer - -# highlight-start -@verify_session() -async def like_comment(request: HttpRequest): - session: SessionContainer = request.supertokens # type: ignore - - user_id = session.get_user_id() - # highlight-end - - print(user_id) -``` - - - - - - - -The `verifySession` function returns a `401` to the frontend if a session doesn't exist, or if the access token has expired, in which case, our frontend SDK automatically refreshes the session. - -In case of successful session verification, you get access to a `session` object using which you can get the user's ID, or manipulate the session information. - - - - - - - -### Microservice authentication - -For authentication between microservices on your backend, checkout the [microservice auth guide](/docs/microservice_auth/client-credentials). - - - -## Protecting frontend routes - - - - - - - - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```tsx -import Session from 'supertokens-web-js/recipe/session'; - -async function doesSessionExist() { - if (await Session.doesSessionExist()) { - // user is logged in - } else { - // user has not logged in yet - } -} -``` - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```tsx -import Session from 'supertokens-web-js-script/recipe/session'; -async function doesSessionExist() { - if (await Session.doesSessionExist()) { - // user is logged in - } else { - // user has not logged in yet - } -} -``` - - - - - - - - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```tsx -import SuperTokens from 'supertokens-react-native'; - -async function doesSessionExist() { - if (await SuperTokens.doesSessionExist()) { - // user is logged in - } else { - // user has not logged in yet - } -} -``` - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```kotlin -import android.app.Application -import com.supertokens.session.SuperTokens - -class MainApplication: Application() { - fun doesSessionExist() { - if (SuperTokens.doesSessionExist(this.applicationContext)) { - // user is logged in - } else { - // user has not logged in yet - } - } -} -``` - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```swift -import UIKit -import SuperTokensIOS - -fileprivate class ViewController: UIViewController { - func doesSessionExist() { - if SuperTokens.doesSessionExist() { - // User is logged in - } else { - // User is not logged in - } - } -} -``` - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```dart -import 'package:supertokens_flutter/supertokens.dart'; - -Future doesSessionExist() async { - return await SuperTokens.doesSessionExist(); -} -``` - - - - - - - - - - - - - - - -## See also - -- Optional sessions [for APIs](../common-customizations/sessions/session-verification-in-api/verify-session#optional-session-verification) and [the frontend](../common-customizations/sessions/checking-session-front-end) -- [Verifying session without using a middleware](../common-customizations/sessions/session-verification-in-api/get-session) -- Session claim validation [for APIs](../common-customizations/sessions/claims/claim-validators) and [the frontend](../common-customizations/sessions/claims/claim-validators) -- [Changing session lifetime](../common-customizations/sessions/change-session-timeout) -- [Sharing session across sub domains](../common-customizations/sessions/share-sessions-across-sub-domains) - - diff --git a/v2/thirdpartyemailpassword/custom-ui/sign-out.mdx b/v2/thirdpartyemailpassword/custom-ui/sign-out.mdx index a3f2af43b..81b1a86e9 100644 --- a/v2/thirdpartyemailpassword/custom-ui/sign-out.mdx +++ b/v2/thirdpartyemailpassword/custom-ui/sign-out.mdx @@ -4,135 +4,9 @@ title: Implementing sign out hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" -import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" -import TabItem from '@theme/TabItem'; -import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" -# Implementing sign out + -The `signOut` method revokes the session on the frontend and on the backend. Calling this function without a valid session also yields a successful response. - - - - - - -```tsx -import Session from "supertokens-web-js/recipe/session"; - -async function logout () { - // highlight-next-line - await Session.signOut(); - window.location.href = "/auth"; // or to wherever your logic page is -} -``` - - - - -```tsx -import supertokensSession from "supertokens-web-js-script/recipe/session"; -async function logout () { - // highlight-next-line - await supertokensSession.signOut(); - window.location.href = "/auth"; // or to wherever your logic page is -} -``` - - - - - - - - - - - - -```tsx -import SuperTokens from "supertokens-react-native"; - -async function logout () { - // highlight-next-line - await SuperTokens.signOut(); - // navigate to the login screen.. -} -``` - - - - - -```kotlin -import android.app.Application -import com.supertokens.session.SuperTokens - -class MainApplication: Application() { - fun logout() { - // highlight-next-line - SuperTokens.signOut(this); - // navigate to the login screen.. - } -} -``` - - - - - -```swift -import UIKit -import SuperTokensIOS - -fileprivate class ViewController: UIViewController { - func signOut() { - SuperTokens.signOut(completionHandler: { - error in - - if error != nil { - // handle error - } else { - // Signed out successfully - } - }) - } -} -``` - - - - - -```dart -import 'package:supertokens_flutter/supertokens.dart'; - -Future signOut() async { - await SuperTokens.signOut( - completionHandler: (error) { - // handle error if any - } - ); -} -``` - - - - - - - - - -- On success, the `signOut` function does not redirect the user to another page, so you must redirect the user yourself. -- The `signOut` function calls the signout API exposed by the session recipe on the backend. -- If you call the `signOut` function whilst the access token has expired, but the refresh token still exists, our SDKs will do an automatic session refresh before revoking the session. - -## See also - -- [Revoking a session on the backend](../common-customizations/sessions/revoke-session) \ No newline at end of file diff --git a/v2/thirdpartyemailpassword/custom-ui/thirdparty-login.mdx b/v2/thirdpartyemailpassword/custom-ui/thirdparty-login.mdx index ac1bf9cdc..903281af2 100644 --- a/v2/thirdpartyemailpassword/custom-ui/thirdparty-login.mdx +++ b/v2/thirdpartyemailpassword/custom-ui/thirdparty-login.mdx @@ -4,1179 +4,9 @@ title: Social login hide_title: true --- - - +import Redirector from '/src/components/Redirector'; - - - -import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" -import TabItem from '@theme/TabItem'; -import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" -import AppInfoForm from "/src/components/appInfoForm" -import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; + -# Social login -There are different flows that the third party login provider may support: -- _**Flow 1)**_ Via authorisation code (one time use code) - for web and mobile apps. - - _**a)**_ If you have configred the client secret on the backend: - - The frontend sends the auth code to the backend, the backend exchanges that with the provided client secret to get the access token. The access token is then used to fetch user info and log them in. - - _**b)**_ If you have **not** provided the client secret on the backend: - - The backend uses PKCE flow to exchange the auth code with the user's access token. The access token is then used to fetch user info and log them in. -- _**Flow 2)**_ Via OAuth / access tokens - for mobile apps. - - The access token is available on the frontend and is sent to the backend. SuperTokens then fetches user info using the access token and logs them in. - -:::note -The same flow applies during sign up and sign in. If the user is signing up, the `createdNewUser` boolean on the frontend and backend will be `true` (as the result of the sign in up API call). -::: - - - - -

      Flow 1a: Authorization code grant flow (Sign in with Google example)

      - -

      Step 1) Redirecting to social / SSO provider

      - -The first step is to fetch the URL on which the user will be authenticated. This can be done by querying the backend API exposed by SuperTokens (as shown below). The backend SDK automatically appends the right query params to the URL (like scope, client ID etc). - -After we get the URL, we simply redirect the user there. In the code below, we will take an example of login with Google: - - - - -```tsx -import { getAuthorisationURLWithQueryParamsAndSetState } from "supertokens-web-js/recipe/thirdparty"; - -async function googleSignInClicked() { - try { - const authUrl = await getAuthorisationURLWithQueryParamsAndSetState({ - thirdPartyId: "google", - - // This is where Google should redirect the user back after login or error. - // This URL goes on the Google's dashboard as well. - frontendRedirectURI: "http:///auth/callback/google", - }); - - /* - Example value of authUrl: https://accounts.google.com/o/oauth2/v2/auth/oauthchooseaccount?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email&access_type=offline&include_granted_scopes=true&response_type=code&client_id=1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com&state=5a489996a28cafc83ddff&redirect_uri=https%3A%2F%2Fsupertokens.io%2Fdev%2Foauth%2Fredirect-to-app&flowName=GeneralOAuthFlow - */ - - // we redirect the user to google for auth. - window.location.assign(authUrl); - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - -```tsx -import supertokensThirdParty from "supertokens-web-js-script/recipe/thirdparty"; -async function googleSignInClicked() { - try { - const authUrl = await supertokensThirdParty.getAuthorisationURLWithQueryParamsAndSetState({ - thirdPartyId: "google", - - // This is where Google should redirect the user back after login or error. - // This URL goes on the Google's dashboard as well. - frontendRedirectURI: "http:///auth/callback/google", - }); - - /* - Example value of authUrl: https://accounts.google.com/o/oauth2/v2/auth/oauthchooseaccount?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email&access_type=offline&include_granted_scopes=true&response_type=code&client_id=1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com&state=5a489996a28cafc83ddff&redirect_uri=https%3A%2F%2Fsupertokens.io%2Fdev%2Foauth%2Fredirect-to-app&flowName=GeneralOAuthFlow - */ - - // we redirect the user to google for auth. - window.location.assign(authUrl); - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - -

      Step 2) Handling the auth callback on your frontend

      - -Once the third party provider redirects your user back to your app, you need to consume the information to sign in the user. This requires you to: -- Setup a route in your app that will handle this callback. We recommend something like `http:///auth/callback/google` (for Google). Regardless of what you make this path, remember to use that same path when calling the `getAuthorisationURLWithQueryParamsAndSetState` function in the first step. - -- On that route, call the following function on page load - - - - - ```tsx - import { signInAndUp } from "supertokens-web-js/recipe/thirdparty"; - - async function handleGoogleCallback() { - try { - const response = await signInAndUp(); - - if (response.status === "OK") { - console.log(response.user) - if (response.createdNewRecipeUser && response.user.loginMethods.length === 1) { - // sign up successful - } else { - // sign in successful - } - window.location.assign("/home"); - } else if (response.status === "SIGN_IN_UP_NOT_ALLOWED") { - // the reason string is a user friendly message - // about what went wrong. It can also contain a support code which users - // can tell you so you know why their sign in / up was not allowed. - window.alert(response.reason) - } else { - // SuperTokens requires that the third party provider - // gives an email for the user. If that's not the case, sign up / in - // will fail. - - // As a hack to solve this, you can override the backend functions to create a fake email for the user. - - window.alert("No email provided by social login. Please use another form of login"); - window.location.assign("/auth"); // redirect back to login page - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } - } - ``` - - - - - ```tsx - import supertokensThirdParty from "supertokens-web-js-script/recipe/thirdparty"; - async function handleGoogleCallback() { - try { - const response = await supertokensThirdParty.signInAndUp(); - - if (response.status === "OK") { - console.log(response.user) - if (response.createdNewRecipeUser && response.user.loginMethods.length === 1) { - // sign up successful - } else { - // sign in successful - } - window.location.assign("/home"); - } else if (response.status === "SIGN_IN_UP_NOT_ALLOWED") { - // the reason string is a user friendly message - // about what went wrong. It can also contain a support code which users - // can tell you so you know why their sign in / up was not allowed. - window.alert(response.reason) - } else { - // SuperTokens requires that the third party provider - // gives an email for the user. If that's not the case, sign up / in - // will fail. - - // As a hack to solve this, you can override the backend functions to create a fake email for the user. - - window.alert("No email provided by social login. Please use another form of login"); - window.location.assign("/auth"); // redirect back to login page - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } - } - ``` - - - - -:::note -On success, the backend will send back session tokens as part of the response headers which will be automatically handled by our frontend SDK for you. -::: - -

      Special case for login with Apple

      - - - -Unlike other providers, Apple will not redirect your user back to your frontend app. Instead, it will redirect the user to your backend with a `FORM POST` request. This means that the URL that you configure on the Apple's dashboard should point to your backend API layer in which **our middleware** will handle the request and redirect the user to your frontend app. Your frontend app should then call the `signInAndUp` API on that page as shown previously. - -In order to tell SuperTokens which frontend route to redirect the user back to, you need to set the `frontendRedirectURI` to the frontend route (just like for other providers), and also need to set the `redirectURIOnProviderDashboard` to point to your backend API route (to which Apple will send a POST request). - - - - -```tsx -import { getAuthorisationURLWithQueryParamsAndSetState } from "supertokens-web-js/recipe/thirdparty"; - -async function appleSignInClicked() { - try { - const authUrl = await getAuthorisationURLWithQueryParamsAndSetState({ - thirdPartyId: "apple", - - frontendRedirectURI: "http://localhost:3000/auth/callback/apple", // This is an example callback URL on your frontend. You can use another path as well. - // highlight-start - redirectURIOnProviderDashboard: "^{form_apiDomain}^{form_apiBasePath}/callback/apple", // This URL goes on the Apple's dashboard - // highlight-end - }); - - // we redirect the user to apple for auth. - window.location.assign(authUrl); - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - -```tsx -import supertokensThirdParty from "supertokens-web-js-script/recipe/thirdparty"; -async function appleSignInClicked() { - try { - const authUrl = await supertokensThirdParty.getAuthorisationURLWithQueryParamsAndSetState({ - thirdPartyId: "apple", - - frontendRedirectURI: "http://localhost:3000/auth/callback/apple", // This is an example callback URL on your frontend. You can use another path as well. - // highlight-start - redirectURIOnProviderDashboard: "^{form_apiDomain}^{form_apiBasePath}/callback/apple", // This URL goes on the Apple's dashboard - // highlight-end - }); - - /* - Example value of authUrl: https://accounts.google.com/o/oauth2/v2/auth/oauthchooseaccount?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email&access_type=offline&include_granted_scopes=true&response_type=code&client_id=1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com&state=5a489996a28cafc83ddff&redirect_uri=https%3A%2F%2Fsupertokens.io%2Fdev%2Foauth%2Fredirect-to-app&flowName=GeneralOAuthFlow - */ - - // we redirect the user to google for auth. - window.location.assign(authUrl); - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - - - -

      Flow 1b: Authorization code grant flow with PKCE

      - -This is identical to flow 1a, except that you do **not** need to provide a client secret during backend init. This flow only works for providers which support the [PKCE flow](https://oauth.net/2/pkce/). - -

      Flow 2: Via OAuth / Access token

      - -:::caution -This flow is not applicable for web apps. -::: - -
      - - - -

      Flow 1a: Authorization code grant flow

      - -

      Sign in with Apple example

      - - - - - -

      Step 1) Fetching the authorisation token on the frontend

      - -For react native apps, this involves setting up the [react-native-apple-authentication library](https://github.com/invertase/react-native-apple-authentication) in your app. Checkout their `README` for steps on how to integrate their SDK into your application. The minimum scope required by SuperTokens is the one that gives the user's email (In case of Apple, that could be the user's actual email or the proxy email provided by Apple - it doesn't really matter). - -Once the integration is done, you should call the `appleAuth.performRequest` function for iOS and the `appleAuthAndroid.signIn` function for Android. Either way, the result of the function will be a one time use auth code which you should send to your backend as shown in the next step. - -A full example of this can be found in [our example app](https://github.com/supertokens/supertokens-react-native/blob/master/examples/with-thirdparty/apple.ts). - -In case you are using Expo, you can use the [expo-apple-authentication](https://docs.expo.dev/versions/latest/sdk/apple-authentication/) library instead (not that this library only works on iOS). - -
      - - - -

      Step 1) Fetching the authorisation token on the frontend

      - -:::info -Coming Soon -::: - -
      - - - -

      Step 1) Fetching the authorisation token on the frontend

      - -For iOS you use the normal sign in with apple flow and then use the authorization code to login with SuperTokens. You can see a full example of this in the `onAppleClicked` function in [our example app](https://github.com/supertokens/supertokens-ios/blob/master/examples/with-thirdparty/with-thirdparty/LoginScreen/LoginScreenViewController.swift). - -```swift -import UIKit -import AuthenticationServices - -fileprivate class ViewController: UIViewController, ASAuthorizationControllerPresentationContextProviding, ASAuthorizationControllerDelegate { - func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor { - return view.window! - } - - func loginWithApple() { - let authorizationRequest = ASAuthorizationAppleIDProvider().createRequest() - authorizationRequest.requestedScopes = [.email, .fullName] - - let authorizationController = ASAuthorizationController(authorizationRequests: [authorizationRequest]) - - authorizationController.presentationContextProvider = self - authorizationController.delegate = self - authorizationController.performRequests() - } - - func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { - guard let credential: ASAuthorizationAppleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential, - let authorizationCode = credential.authorizationCode, - let authorizationCodeString: String = String(data: authorizationCode, encoding: .utf8), - let email: String = credential.email as? String, - let nameComponents: PersonNameComponents = credential.fullName as? PersonNameComponents, - let firstName: String = nameComponents.givenName as? String, - let lastName: String = nameComponents.familyName as? String else {return} - - // Send the user information and auth code to the backend. Refer to the next step. - } -} -``` - -
      - - - -

      Step 1) Fetching the authorisation token on the frontend

      - -For flutter we use the [sign_in_with_apple](https://pub.dev/packages/sign_in_with_apple) package, make sure to follow the prerequisites steps to get the package setup. After setup use the snippet below to trigger the apple sign in flow. You can see a full example of this in the `loginWithApple` function in [our example app](https://github.com/supertokens/supertokens-flutter/blob/master/examples/with-thirdparty/lib/screens/login.dart). - -```dart -import 'package:sign_in_with_apple/sign_in_with_apple.dart'; - -void loginWithApple() async { - try { - var credential = await SignInWithApple.getAppleIDCredential( - scopes: [ - AppleIDAuthorizationScopes.email, - AppleIDAuthorizationScopes.fullName, - ], - // Required for Android only - webAuthenticationOptions: WebAuthenticationOptions( - clientId: "", - redirectUri: Uri.parse( - "//callback/apple", - ), - ), - ); - - String authorizationCode = credential.authorizationCode; - String? idToken = credential.identityToken; - String? email = credential.email; - String? firstname = credential.givenName; - String? lastName = credential.familyName; - - // Send the user information and auth code to the backend. Refer to the next step. - } catch (e) { - // Sign in aborted or failed - } -} -``` - -In the snippet above for Android we need to pass an additional `webAuthenticationOptions` property when signing in with Apple. This is because on Android the library uses the web login flow and we need to provide it with the client id and redirection uri. The `redirectUri` property here is the URL to which Apple will make a `POST` request after the user has logged in. The SuperTokens backend SDKs provide an API for this at `//callback/apple`. - -

      Additional steps for Android

      - -For android we also need to provide a way for the web login flow to redirect back to the app. By default the API provided by the backend SDKs redirect to the website domain you provide when initialising the SDK, we can override the API to have it redirect to our app instead. For example if you were using the Node.js SDK: - - - - - -```tsx -import ThirdParty from "supertokens-node/recipe/thirdparty"; - -ThirdParty.init({ - // highlight-start - override: { - apis: (original) => { - return { - ...original, - appleRedirectHandlerPOST: async (input) => { - if (original.appleRedirectHandlerPOST === undefined) { - throw Error("Should never come here"); - } - - // inut.formPostInfoFromProvider contains all the query params attached by Apple - const stateInBase64 = input.formPostInfoFromProvider.state; - - // The web SDKs add a default state - if (stateInBase64 === undefined) { - // Redirect to android app - // We create a dummy URL to create the query string - const dummyUrl = new URL("http://localhost:8080"); - for (const [key, value] of Object.entries(input.formPostInfoFromProvider)) { - dummyUrl.searchParams.set(key, `${value}`); - } - - const queryString = dummyUrl.searchParams.toString(); - // Refer to the README of sign_in_with_apple to understand what this url is - const redirectUrl = `intent://callback?${queryString}#Intent;package=YOUR.PACKAGE.IDENTIFIER;scheme=signinwithapple;end`; - - input.options.res.setHeader("Location", redirectUrl, false); - input.options.res.setStatusCode(303); - input.options.res.sendHTMLResponse(""); - } else { - // For the web flow we can use the original implementation - original.appleRedirectHandlerPOST(input); - } - }, - }; - }, - }, - // highlight-end -}) -``` - - - - - -```go -import ( - "net/http" - "strings" - - "github.com/supertokens/supertokens-golang/recipe/thirdparty" - "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" -) - -func main() { - thirdparty.Init(&tpmodels.TypeInput{ - Override: &tpmodels.OverrideStruct{ - // highlight-start - APIs: func(originalImplementation tpmodels.APIInterface) tpmodels.APIInterface { - originalAppleRedirectPost := *originalImplementation.AppleRedirectHandlerPOST - - *originalImplementation.AppleRedirectHandlerPOST = func(formPostInfoFromProvider map[string]interface{}, options tpmodels.APIOptions, userContext *map[string]interface{}) error { - // formPostInfoFromProvider contains all the query params attached by Apple - state, stateOk := formPostInfoFromProvider["state"].(string) - - queryParams := []string{} - if (!stateOk) || state == "" { - // Redirect to android app - for key, value := range formPostInfoFromProvider { - queryParams = append(queryParams, key+"="+value.(string)) - } - - queryString := "" - if len(queryParams) > 0 { - queryString = strings.Join(queryParams, "&") - } - - // Refer to the README of sign_in_with_apple to understand what this url is - redirectUri := "intent://callback?" + queryString + "#Intent;package=YOUR.PACKAGE.IDENTIFIER;scheme=signinwithapple;end" - - options.Res.Header().Set("Location", redirectUri) - options.Res.WriteHeader(http.StatusSeeOther) - return nil - } else { - return originalAppleRedirectPost(formPostInfoFromProvider, options, userContext) - } - } - - return originalImplementation - }, - }, - // highlight-end - }) -} -``` - - - - - -```python -from supertokens_python.recipe import thirdparty -from supertokens_python.recipe.thirdparty.interfaces import APIInterface, APIOptions -from typing import Dict, Any - -# highlight-start -def override_thirdparty_apis(original_implementation: APIInterface): - original_apple_redirect_post = original_implementation.apple_redirect_handler_post - - async def apple_redirect_handler_post( - form_post_info: Dict[str, Any], - api_options: APIOptions, - user_context: Dict[str, Any] - ): - # form_post_info contains all the query params attached by Apple - state = form_post_info["state"] - - # The web SDKs add a default state - if state is None: - query_items = [ - f"{key}={value}" for key, value in form_post_info.items() - ] - - query_string = "&".join(query_items) - - # Refer to the README of sign_in_with_apple to understand what this url is - redirect_url = "intent://callback?" + query_string + "#Intent;package=YOUR.PACKAGE.IDENTIFIER;scheme=signinwithapple;end" - - api_options.response.set_header("Location", redirect_url) - api_options.response.set_status_code(303) - api_options.response.set_html_content("") - else: - return await original_apple_redirect_post(form_post_info, api_options, user_context) - - original_implementation.apple_redirect_handler_post = apple_redirect_handler_post - return original_implementation -# highlight-end - -thirdparty.init( - # highlight-start - override=thirdparty.InputOverrideConfig( - apis=override_thirdparty_apis - ), - # highlight-end -) -``` - - - - - -In the code above we override the `appleRedirectHandlerPOST` API to check if the request was made by our Android app (You can skip checking the state if you only have a mobile app and no website). `sign_in_with_apple` requires us to parse the query params sent by apple and include them in the redirect URL in a specific way, and then we simply redirect to the deep link url. Refer to the README for `sign_in_with_apple` to read about the deep link setup required in Android. - -
      - -
      - -

      Step 2) Calling the signinup API to consume the authorisation token

      - - - -Once you have the authorisation code from the auth provider, you need to call the signinup API exposed by our backend SDK as shown below: - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup' \ ---header 'Content-Type: application/json; charset=utf-8' \ ---data-raw '{ - "thirdPartyId": "apple", - "clientType": "...", - "redirectURIInfo": { - "redirectURIOnProviderDashboard": "^{form_apiDomain}^{form_apiBasePath}/callback/apple", - "redirectURIQueryParams": { - "code": "...", - "user": { - "name":{ - "firstName":"...", - "lastName":"..." - }, - "email":"..." - } - } - } -}' -``` - -:::important -- On iOS, the client id set in the backend should be the same as the bundle identifier for your app. -- The `clientType` input is optional, and is required only if you have initialised more than one client in the provide on the backend (See the "Social / SSO login for both, web and mobile apps" section below). -- On iOS, `redirectURIOnProviderDashboard` doesn't matter and its value can be a universal link configured for your app. -- On Android, the `redirectURIOnProviderDashboard` should match the one configured on the Apple developer dashboard. -- The `user` object contains information provided by Apple. -::: - -The response body from the API call has a `status` property in it: -- `status: "OK"`: User sign in / up was successful. The response also contains more information about the user, for example their user ID, and if it was a new user or existing user. -- `status: "NO_EMAIL_GIVEN_BY_PROVIDER"`: This is returned if the social / SSO provider did not provider an email for the user. In this case, you want to ask the user to pick another method of sign in. Or, you can also override the backend functions to create a fake email for the user for this provider. -- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. -- `status: "SIGN_IN_UP_NOT_ALLOWED"`: This can happen during automatic account linking or during MFA. The `reason` prop that's in the response body contains a support code using which you can see why the sign in / up was not allowed. - -:::note -On success, the backend will send back session tokens as part of the response headers which will be automatically handled by our frontend SDK for you. -::: - - - -

      Sign in with Google Example

      - - - - - -

      Step 1) Fetching the authorisation token on the frontend

      - -This involves setting up the [@react-native-google-signin/google-signin](https://github.com/react-native-google-signin/google-signin) in your app. Checkout their `README` for steps on how to integrate their SDK into your application. The minimum scope required by SuperTokens is the one that gives the user's email. - -Once the library is set up, use `GoogleSignin.configure` and `GoogleSignin.signIn` to trigger the login flow and sign the user in with Google. Refer to [our example app](https://github.com/supertokens/supertokens-react-native/blob/master/examples/with-thirdparty/google.ts) to see the full code for this. - -```tsx -import { GoogleSignin } from "@react-native-google-signin/google-signin"; - -export const performGoogleSignIn = async (): Promise => { - GoogleSignin.configure({ - webClientId: "GOOGLE_WEB_CLIENT_ID", - iosClientId: "GOOGLE_IOS_CLIENT_ID", - }); - - try { - const user = await GoogleSignin.signIn({}); - const authCode = user.serverAuthCode; - - // Refer to step 2 - - return true; - } catch (e) { - console.log("Google sign in failed with error", e); - } - - return false; -}; -``` - -
      - - - -

      Step 1) Fetching the authorisation token on the frontend

      - -Follow the [official Google Sign In guide](https://developers.google.com/identity/sign-in/android/start-integrating) to set up their library and sign the user in with Google. Fetch the authorization code from the google sign in result. For a full example refer to the `signInWithGoogle` function in [our example app](https://github.com/supertokens/supertokens-android/blob/master/examples/with-thirdparty/app/src/main/java/com/supertokens/supertokensexample/LoginActivity.kt). - -```kotlin -import androidx.activity.result.ActivityResult -import androidx.activity.result.ActivityResultLauncher -import androidx.activity.result.contract.ActivityResultContracts -import androidx.appcompat.app.AppCompatActivity -import android.os.Bundle -import android.util.Log -import com.google.android.gms.auth.api.signin.GoogleSignIn -import com.google.android.gms.auth.api.signin.GoogleSignInOptions -import android.content.Intent - -class LoginActivity : AppCompatActivity() { - private lateinit var googleResultLauncher: ActivityResultLauncher - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - googleResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - onGoogleResultReceived(it) - } - } - - private fun signInWithGoogle() { - val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) - .requestServerAuthCode("GOOGLE_WEB_CLIENT_ID") - .requestEmail() - .build() - - val googleClient = GoogleSignIn.getClient(this, gso) - val signInIntent = googleClient.signInIntent - - googleResultLauncher.launch(signInIntent) - } - - private fun onGoogleResultReceived(it: ActivityResult) { - val task = GoogleSignIn.getSignedInAccountFromIntent(it.data) - val account = task.result - val authCode = account.serverAuthCode - - // Refer to step 2 - } -} -``` - -
      - - - -

      Step 1) Fetching the authorisation token on the frontend

      - -For iOS we use the `GoogleSignIn` library, follow the [official guide](https://developers.google.com/identity/sign-in/ios/start-integrating) to set up the library and sign the user in with Google. Use the result of google sign in to get the authorization code. For a full example refer to the `onGoogleCliked` function in [our example app](https://github.com/supertokens/supertokens-ios/blob/master/examples/with-thirdparty/with-thirdparty/LoginScreen/LoginScreenViewController.swift). - -```swift -import UIKit -import GoogleSignIn - -fileprivate class LoginScreenViewController: UIViewController { - @IBAction func onGoogleCliked() { - GIDSignIn.sharedInstance.signIn(withPresenting: self) { signInResult, error in - guard error == nil else { return } - - guard let authCode: String = signInResult?.serverAuthCode as? String else { - print("Google login did not return an authorisation code") - return - } - - // Refer to step 2 - } - } -} -``` - -
      - - - -

      Step 1) Fetching the authorisation token on the frontend

      - -For flutter we use the [google_sign_in](https://pub.dev/packages/google_sign_in) package, make sure to follow the prerequisites steps to get the package setup. After setup use the snippet below to trigger the google sign in flow. For a full example refer to the `loginWithGoogle` in [our example app](https://github.com/supertokens/supertokens-flutter/blob/master/examples/with-thirdparty/lib/screens/login.dart). - -```dart -import 'package:google_sign_in/google_sign_in.dart'; -import 'dart:io'; - -Future loginWithGoogle() async { - GoogleSignIn googleSignIn; - - if (Platform.isAndroid) { - googleSignIn = GoogleSignIn( - serverClientId: "GOOGLE_WEB_CLIENT_ID", - scopes: [ - 'email', - ], - ); - } else { - googleSignIn = GoogleSignIn( - clientId: "GOOGLE_IOS_CLIENT_ID", - serverClientId: "GOOGLE_WEB_CLIENT_ID", - scopes: [ - 'email', - ], - ); - } - - GoogleSignInAccount? account = await googleSignIn.signIn(); - - if (account == null) { - print("Google sign in was aborted"); - return; - } - - String? authCode = account.serverAuthCode; - - if (authCode == null) { - print("Google sign in did not return a server auth code"); - return; - } - - // Refer to step 2 - } -``` - -
      - -
      - -

      Step 2) Calling the signinup API to consume the authorisation token

      - - - -Once you have the authorisation code from the auth provider, you need to call the signinup API exposed by our backend SDK as shown below: - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup' \ ---header 'Content-Type: application/json; charset=utf-8' \ ---data-raw '{ - "thirdPartyId": "google", - "clientType": "...", - "redirectURIInfo": { - "redirectURIOnProviderDashboard": "", - "redirectURIQueryParams": { - "code": "...", - } - } -}' -``` - -:::important -When calling the API exposed by the SuperTokens backend SDK, we pass an empty string for `redirectURIOnProviderDashboard` because we use the native login flow using the authorization code which does not involve any redirection on the frontend. -::: - -The response body from the API call has a `status` property in it: -- `status: "OK"`: User sign in / up was successful. The response also contains more information about the user, for example their user ID, and if it was a new user or existing user. -- `status: "NO_EMAIL_GIVEN_BY_PROVIDER"`: This is returned if the social / SSO provider did not provider an email for the user. In this case, you want to ask the user to pick another method of sign in. Or, you can also override the backend functions to create a fake email for the user for this provider. -- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. -- `status: "SIGN_IN_UP_NOT_ALLOWED"`: This can happen during automatic account linking or during MFA. The `reason` prop that's in the response body contains a support code using which you can see why the sign in / up was not allowed. - -:::note -On success, the backend will send back session tokens as part of the response headers which will be automatically handled by our frontend SDK for you. -::: - - - -

      Flow 1b: Authorization code grant flow with PKCE

      - -This is similar to flow 1a, except that you do **not** need to provide a client secret during backend init. This flow only works for providers which support the [PKCE flow](https://oauth.net/2/pkce/). - - - - - -

      Step 1) Fetching the authorisation token on the frontend

      - -You can use the [react native auth library](https://github.com/FormidableLabs/react-native-app-auth) to also return the PKCE code verifier along with the authorization code. This can be done by setting the `usePKCE` boolean to `true` and also by setting the `skipCodeExchange` to `true` when configuring the react native auth library. - -
      - - - -

      Step 1) Fetching the authorisation token on the frontend

      - -You can use the [AppAuth-Android](https://github.com/openid/AppAuth-Android) library to use the PKCE flow by using the `setCodeVerifier` method when creating a `AuthorizationRequest`. - -
      - - - -

      Step 1) Fetching the authorisation token on the frontend

      - -You can use the [AppAuth-iOS](https://github.com/openid/AppAuth-iOS) library to use the PKCE flow. - -
      - - - -

      Step 1) Fetching the authorisation token on the frontend

      - -You can use [flutter_appauth](https://pub.dev/packages/flutter_appauth) to use the PKCE flow by providing a `codeVerifier` when you call the `appAuth.token` function. - -
      - -
      - -

      Step 2) Calling the signinup API to consume the authorisation token

      - - - -Once you have the authorisation code and PKCE verifier from the auth provider, you need to call the signinup API exposed by our backend SDK as shown below: - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "thirdPartyId": "THIRD_PARTY_ID", - "clientType": "...", - "redirectURIInfo": { - "redirectURIOnProviderDashboard": "REDIRECT_URI", - "redirectURIQueryParams": { - "code": "...", - }, - "pkceCodeVerifier": "..." - } -}' -``` - -:::important -- Replace `THIRD_PARTY_ID` with the provider id. The provider id must match the one you configure in the backend when intialising SuperTokens. -- `REDIRECT_URI` must exactly match the value you configure on the providers dashboard. -::: - -The response body from the API call has a `status` property in it: -- `status: "OK"`: User sign in / up was successful. The response also contains more information about the user, for example their user ID, and if it was a new user or existing user. -- `status: "NO_EMAIL_GIVEN_BY_PROVIDER"`: This is returned if the social / SSO provider did not provider an email for the user. In this case, you want to ask the user to pick another method of sign in. Or, you can also override the backend functions to create a fake email for the user for this provider. -- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. -- `status: "SIGN_IN_UP_NOT_ALLOWED"`: This can happen during automatic account linking or during MFA. The `reason` prop that's in the response body contains a support code using which you can see why the sign in / up was not allowed. - -:::note -On success, the backend will send back session tokens as part of the response headers which will be automatically handled by our frontend SDK for you. -::: - - - ----------------- - -

      Flow 2: Via OAuth / Access token

      - -

      Step 1) Fetching the OAuth / access tokens on the frontend

      - -1. Sign in with the social provider. The minimum required scope is the one that provides access to the user's email. You can use any library to sign in with the social provider. -2. Get the access token on the frontend if it is available. -3. Get the id token from the sign in result if it is available. - -:::important -You need to provide either the access token or the id token, or both in step 2, depending on what is available. -::: - -

      Step 2) Calling the signinup API to use the OAuth tokens

      - - - -Once you have the `access_token` or the `id_token` from the auth provider, you need to call the signinup API exposed by our backend SDK as shown below: - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup' \ ---header 'Content-Type: application/json; charset=utf-8' \ ---data-raw '{ - "thirdPartyId": "google", - "clientType": "...", - "oAuthTokens": { - "access_token": "...", - "id_token": "..." - }, -}' -``` - -:::important -- The `clientType` input is optional, and is required only if you have initialised more than one client in the provide on the backend (See the "Social / SSO login for both, web and mobile apps" section below). -- If you have the `id_token`, you can send that along with the `access_token`. -::: - -The response body from the API call has a `status` property in it: -- `status: "OK"`: User sign in / up was successful. The response also contains more information about the user, for example their user ID, and if it was a new user or existing user. -- `status: "NO_EMAIL_GIVEN_BY_PROVIDER"`: This is returned if the social / SSO provider did not provider an email for the user. In this case, you want to ask the user to pick another method of sign in. Or, you can also override the backend functions to create a fake email for the user for this provider. -- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. -- `status: "SIGN_IN_UP_NOT_ALLOWED"`: This can happen during automatic account linking or during MFA. The `reason` prop that's in the response body contains a support code using which you can see why the sign in / up was not allowed. - -:::note -On success, the backend will send back session tokens as part of the response headers which will be automatically handled by our frontend SDK for you. -::: - - - -
      - -
      - -## Social / SSO login for both, web and mobile apps - -If you have social / SSO login for your web and mobile app, then you might need to setup different client ID / secret for the same provider on the backend. For example, in case of Apple login, Apple gives you different client IDs for iOS login vs web & Android login (same client ID for web and Android). - -In order to get this to work, you would need to add additional clients to the Apple.init on the backend. Each client would need to be uniquely identified and this is done using the `clientType` string. For example, you can add one `clientType` for `web-and-android` and one for `ios`. - - - - -```tsx -import { ProviderInput } from "supertokens-node/recipe/thirdparty/types"; - -let providers: ProviderInput[] = [ - { - config: { - thirdPartyId: "apple", - clients: [{ - clientType: "web-and-android", - clientId: "...", - additionalConfig: { - "keyId": "...", - "privateKey": "...", - "teamId": "...", - } - }, { - clientType: "ios", - clientId: "...", - additionalConfig: { - "keyId": "...", - "privateKey": "...", - "teamId": "...", - } - }] - } - } -] -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" -) - -func main() { - _ = []tpmodels.ProviderInput{{ - Config: tpmodels.ProviderConfig{ - ThirdPartyId: "apple", - Clients: []tpmodels.ProviderClientConfig{ - { - ClientType: "web-and-android", - ClientID: "...", - AdditionalConfig: map[string]interface{}{ - "keyId": "...", - "privateKey": "...", - "teamId": "...", - }, - }, - { - ClientType: "ios", - ClientID: "...", - AdditionalConfig: map[string]interface{}{ - "keyId": "...", - "privateKey": "...", - "teamId": "...", - }, - }, - }, - }, - }} -} -``` - - - - -```python -from supertokens_python.recipe.thirdparty.provider import ProviderInput, ProviderConfig, ProviderClientConfig - -providers = [ - ProviderInput( - config=ProviderConfig( - third_party_id="apple", - clients=[ - ProviderClientConfig( - client_type="web-and-android", - client_id="...", - additional_config={ - "keyId": "...", - "privateKey": "...", - "teamId": "...", - }, - ), - ProviderClientConfig( - client_type="ios", - client_id="...", - additional_config={ - "keyId": "...", - "privateKey": "...", - "teamId": "...", - }, - ), - ], - ), - ), -] -``` - - - - -For the frontend, you would need to use the right `clientType` as shown below: - - - - - - - - - -We pass in the `clientType` during the init call. - -```tsx -import SuperTokens from 'supertokens-web-js'; - -SuperTokens.init({ - appInfo: { - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}", - appName: "...", - }, - // highlight-next-line - clientType: "web-and-android", - recipeList: [/*...*/], -}); -``` - -If you are using our pre built UI SDK (supertokens-auth-react) as well, you can provide the `clientType` config to it as follows: - -```tsx -import SuperTokens from 'supertokens-auth-react'; - -SuperTokens.init({ - appInfo: { - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - appName: "...", - }, - // highlight-next-line - clientType: "web-and-android", - recipeList: [/*...*/], -}); -``` - - - - - - - - -We pass in the `clientType` during the init call. - -```tsx -import supertokens from "supertokens-web-js-script"; -supertokens.init({ - appInfo: { - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}", - appName: "...", - }, - // highlight-next-line - clientType: "web-and-android", - recipeList: [/*...*/], -}); -``` - - - - - - - - - - -When making calls to the APIs from your mobile app, the request body also takes a `clientType` prop as seen in the above API calls. - - - - - - -## See also - -- [Post sign in / sign up action](../common-customizations/handling-signinup-success) -- [Changing OAuth provider's scope](../common-customizations/signup-form/built-in-providers#setting-oauth-scopes) -- [See all built in providers](../common-customizations/signup-form/built-in-providers) -- [Integrating with a custom OAuth provider](../common-customizations/signup-form/custom-providers) -- [Fetching profile info and using the provider's access token on the backend](../common-customizations/get-user-info) -- [Customising user ID format](../common-customizations/userid-format) \ No newline at end of file diff --git a/v2/thirdpartyemailpassword/introduction.mdx b/v2/thirdpartyemailpassword/introduction.mdx index fafe11bbb..1b61d2fbd 100644 --- a/v2/thirdpartyemailpassword/introduction.mdx +++ b/v2/thirdpartyemailpassword/introduction.mdx @@ -1,46 +1,12 @@ --- id: introduction -title: Introduction & Architecture +title: Introduction hide_title: true +hide_table_of_contents: true --- -# EmailPassword with Social / Enterprise (OAuth 2.0, SAML) login +import Redirector from '/src/components/Redirector'; -## Features -- Sign-up / Sign-in with email and password or with [OAuth 2.0 / OIDC / SAML providers](https://supertokens.com/features/single-sign-on) (Like Google, Facebook, Active Directory, etc)
      -- Forgot password flow using email
      -- Secure session management
      -- Email verification
      + - - - - -## Demo application - -- See our [live demo app](https://^{docsLinkRecipeName}.demo.supertokens.com/). -- We have a read-only user management dashboard (with fake data), and it can be accessed [on this link](https://dashboard.demo.supertokens.com/api/auth/dashboard). The credentials for logging in are: - ```text - email: demo@supertokens.com - password: abcd1234 - ``` -- Generate a starter app - ```bash - npx create-supertokens-app@latest --recipe=^{docsLinkRecipeName} - ``` -- The user management dashboard for the starter app can be accessed on `http://localhost:3001/auth/dashboard`. You will have to first create users (instrs are on the dashboard UI), before logging in. - -## Architecture - -import Architecture from "../community/reusableMD/intro-architecture.mdx" - - - -## Next steps - -import NextSteps from "../community/reusableMD/intro-next-steps.mdx" - - - - diff --git a/v2/thirdpartyemailpassword/pre-built-ui/enable-email-verification.mdx b/v2/thirdpartyemailpassword/pre-built-ui/enable-email-verification.mdx index 3b5ca11e8..4f3ade3f4 100644 --- a/v2/thirdpartyemailpassword/pre-built-ui/enable-email-verification.mdx +++ b/v2/thirdpartyemailpassword/pre-built-ui/enable-email-verification.mdx @@ -4,814 +4,8 @@ title: Enable email verification hide_title: true --- -# Enable email verification +import Redirector from '/src/components/Redirector'; -:::important -For social / third party logins, the user's email is automatically marked as verified if the user had verified their email to the login provider. -::: - - - + -import CustomAdmonition from "/src/components/customAdmonition" -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"; - - -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 - - - - -```tsx -import SuperTokens from "supertokens-node"; -import EmailVerification from "supertokens-node/recipe/emailverification"; -import Session from "supertokens-node/recipe/session"; - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // highlight-start - EmailVerification.init({ - mode: "REQUIRED", // or "OPTIONAL" - }), - // highlight-end - Session.init(), - ], -}); -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/emailverification" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evmodels" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - supertokens.Init(supertokens.TypeInput{ - RecipeList: []supertokens.Recipe{ - // highlight-start - emailverification.Init(evmodels.TypeInput{ - Mode: evmodels.ModeRequired, // or evmodels.ModeOptional - }), - // highlight-end - session.Init(&sessmodels.TypeInput{}), - }, - }) -} -``` - - - - -```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import session -from supertokens_python.recipe import emailverification - -init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore - recipe_list=[ - # highlight-start - emailverification.init(mode='REQUIRED'), # or 'OPTIONAL' - # highlight-end - session.init() - ] -) -``` - - - - - - - - - - -import {Question}from "/src/components/question" - -## Step 2: Frontend setup - - - - - - - -```tsx -import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; -import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui"; -import EmailVerification from "supertokens-auth-react/recipe/emailverification"; -^{prebuiltuiimport} -import { EmailVerificationPreBuiltUI } from "supertokens-auth-react/recipe/emailverification/prebuiltui"; -import Session from "supertokens-auth-react/recipe/session"; -import reactRouterDOM, { Routes, BrowserRouter as Router, Route } from "react-router-dom"; - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // highlight-start - EmailVerification.init({ - mode: "REQUIRED", // or "OPTIONAL" - }), - // highlight-end - Session.init(), - ], -}); - -function App() { - return ( - -
      - -
      - - // highlight-start - {getSuperTokensRoutesForReactRouterDom(reactRouterDOM, [^{recipePreBuiltUINameCapitalLetters}, EmailVerificationPreBuiltUI])} - // highlight-end - // ... other routes - -
      -
      -
      -
      - ); -} -``` - -
      - - - -```tsx -import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; -import { canHandleRoute, getRoutingComponent } from "supertokens-auth-react/ui"; -import EmailVerification from "supertokens-auth-react/recipe/emailverification"; -^{prebuiltuiimport} -import { EmailVerificationPreBuiltUI } from "supertokens-auth-react/recipe/emailverification/prebuiltui"; -import Session from "supertokens-auth-react/recipe/session"; - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // highlight-start - EmailVerification.init({ - mode: "REQUIRED", // or "OPTIONAL" - }), - // highlight-end - Session.init(), - ], -}); - -function App() { - // highlight-start - if (canHandleRoute([^{recipePreBuiltUINameCapitalLetters}, EmailVerificationPreBuiltUI])) { - return getRoutingComponent([^{recipePreBuiltUINameCapitalLetters}, EmailVerificationPreBuiltUI]) - } - // highlight-end - return ( - {/*Your app*/} - ); -} -``` - - - -
      - -
      - - - -```tsx -// this goes in the auth route config of your frontend app (once the pre built UI script has been loaded) -import {init as supertokensUIInit} from "supertokens-auth-react-script"; -import supertokensUIEmailVerification from "supertokens-auth-react-script/recipe/emailverification"; -import supertokensUISession from "supertokens-auth-react-script/recipe/session"; - -supertokensUIInit({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // highlight-start - supertokensUIEmailVerification.init({ - mode: "REQUIRED", // or "OPTIONAL" - }), - // highlight-end - supertokensUISession.init(), - ], -}); -``` - - - -
      - - - - - - - -## Step 3: Checking if the user's email is verified in your APIs - -

      If using REQUIRED mode

      - -On the backend, when you initialize the email verification recipe in this mode, the `verifySession` middleware automatically checks if the user's email is verified based on the contents of the session's payload. If the email is not verified, the `verifySession` middleware will return a `403` status code to the client. - -

      If using OPTIONAL mode

      - -In this mode, you need to check if the email is verified yourself in the APIs in which you want this constraint. The verification status should already be in the session's payload. - - - - - - -```tsx -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import express from "express"; -import { SessionRequest } from "supertokens-node/framework/express"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let app = express(); - -app.post( - "/update-blog", - verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - }), - async (req: SessionRequest, res) => { - // All validator checks have passed and the user has a verified email address - } -); -``` - - - -```tsx -import Hapi from "@hapi/hapi"; -import { verifySession } from "supertokens-node/recipe/session/framework/hapi"; -import {SessionRequest} from "supertokens-node/framework/hapi"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let server = Hapi.server({ port: 8000 }); - -server.route({ - path: "/update-blog", - method: "post", - options: { - pre: [ - { - method: verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - }), - }, - ], - }, - handler: async (req: SessionRequest, res) => { - // All validator checks have passed and the user has a verified email address - } -}) -``` - - - -```tsx -import Fastify from "fastify"; -import { verifySession } from "supertokens-node/recipe/session/framework/fastify"; -import { SessionRequest } from "supertokens-node/framework/fastify"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let fastify = Fastify(); - -fastify.post("/update-blog", { - preHandler: verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - }), -}, async (req: SessionRequest, res) => { - // All validator checks have passed and the user has a verified email address -}); -``` - - - - -```tsx -import { verifySession } from "supertokens-node/recipe/session/framework/awsLambda"; -import { SessionEvent } from "supertokens-node/framework/awsLambda"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -async function updateBlog(awsEvent: SessionEvent) { - // All validator checks have passed and the user has a verified email address -}; - -exports.handler = verifySession(updateBlog, { - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] -}); -``` - - - - -```tsx -import KoaRouter from "koa-router"; -import { verifySession } from "supertokens-node/recipe/session/framework/koa"; -import {SessionContext} from "supertokens-node/framework/koa"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let router = new KoaRouter(); - -router.post("/update-blog", verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - }), async (ctx: SessionContext, next) => { - // All validator checks have passed and the user has a verified email address -}); -``` - - - - -```tsx -import { inject, intercept } from "@loopback/core"; -import { RestBindings, MiddlewareContext, post, response } from "@loopback/rest"; -import { verifySession } from "supertokens-node/recipe/session/framework/loopback"; -import Session from "supertokens-node/recipe/session"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -class SetRole { - constructor(@inject(RestBindings.Http.CONTEXT) private ctx: MiddlewareContext) { } - @post("/update-blog") - @intercept(verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - })) - @response(200) - async handler() { - // All validator checks have passed and the user has a verified email address - } -} -``` - - - - -```tsx -import { superTokensNextWrapper } from 'supertokens-node/nextjs' -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import { SessionRequest } from "supertokens-node/framework/express"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -// highlight-start -export default async function setRole(req: SessionRequest, res: any) { - await superTokensNextWrapper( - async (next) => { - await verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - })(req, res, next); - }, - req, - res - ) - // All validator checks have passed and the user has a verified email address -} -``` - - - - -```tsx -import SuperTokens from "supertokens-node"; -import { NextResponse, NextRequest } from "next/server"; -import { withSession } from "supertokens-node/nextjs"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; -// @ts-ignore -import { backendConfig } from "@/app/config/backend"; - -SuperTokens.init(backendConfig()); - -export async function POST(request: NextRequest) { - return withSession(request, async (err, session) => { - if (err) { - return NextResponse.json(err, { status: 500 }); - } - // All validator checks have passed and the user has a verified email address - return NextResponse.json({ message: "Your email is verified!" }); - }, - { - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - } - ); -} -``` - - - - -```tsx -import { Controller, Post, UseGuards, Request, Response, Session } from "@nestjs/common"; -import { SessionContainer, SessionClaimValidator } from "supertokens-node/recipe/session"; -// @ts-ignore -import { AuthGuard } from './auth/auth.guard'; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -@Controller() -export class ExampleController { - @Post('example') - @UseGuards(new AuthGuard({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators: SessionClaimValidator[]) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - })) - async postExample(@Session() session: SessionContainer): Promise { - // All validator checks have passed and the user has a verified email address - return true; - } -} -``` - - - - - - - - -```go -import ( - "net/http" - - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - _ = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - session.VerifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }, exampleAPI).ServeHTTP(rw, r) - }) -} - -func exampleAPI(w http.ResponseWriter, r *http.Request) { - // TODO: session is verified and all validators have passed.. -} -``` - - - - -```go -import ( - "net/http" - - "github.com/gin-gonic/gin" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - router := gin.New() - - // Wrap the API handler in session.VerifySession - router.POST("/likecomment", verifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }), exampleAPI) -} - -// This is a function that wraps the supertokens verification function -// to work the gin -func verifySession(options *sessmodels.VerifySessionOptions) gin.HandlerFunc { - return func(c *gin.Context) { - session.VerifySession(options, func(rw http.ResponseWriter, r *http.Request) { - c.Request = c.Request.WithContext(r.Context()) - c.Next() - })(c.Writer, c.Request) - // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly - c.Abort() - } -} - -func exampleAPI(c *gin.Context) { - // TODO: session is verified and all claim validators pass. -} -``` - - - - -```go -import ( - "net/http" - - "github.com/go-chi/chi" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - r := chi.NewRouter() - - // Wrap the API handler in session.VerifySession - r.Post("/likecomment", session.VerifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }, exampleAPI)) -} - -func exampleAPI(w http.ResponseWriter, r *http.Request) { - // TODO: session is verified and all claim validators pass. -} -``` - - - - -```go -import ( - "net/http" - - "github.com/gorilla/mux" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - router := mux.NewRouter() - - // Wrap the API handler in session.VerifySession - router.HandleFunc("/likecomment", session.VerifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }, exampleAPI)).Methods(http.MethodPost) -} - -func exampleAPI(w http.ResponseWriter, r *http.Request) { - // TODO: session is verified and all claim validators pass. -} -``` - - - - - - - - - -```python -from supertokens_python.recipe.session.framework.fastapi import verify_session -from supertokens_python.recipe.emailverification import EmailVerificationClaim -from supertokens_python.recipe.session import SessionContainer -from fastapi import Depends - -@app.post('/like_comment') # type: ignore -async def like_comment(session: SessionContainer = Depends( - verify_session( - # highlight-start - # We add the EmailVerificationClaim's is_verified validator - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \ - [EmailVerificationClaim.validators.is_verified()] - # highlight-end - ) -)): - # All validator checks have passed and the user has a verified email address - pass -``` - - - - -```python -from supertokens_python.recipe.session.framework.flask import verify_session -from supertokens_python.recipe.emailverification import EmailVerificationClaim - -@app.route('/update-jwt', methods=['POST']) # type: ignore -@verify_session( - # highlight-start - # We add the EmailVerificationClaim's is_verified validator - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \ - [EmailVerificationClaim.validators.is_verified()] - # highlight-end -) -def like_comment(): - # All validator checks have passed and the user has a verified email address - pass -``` - - - - -```python -from supertokens_python.recipe.session.framework.django.asyncio import verify_session -from django.http import HttpRequest -from supertokens_python.recipe.emailverification import EmailVerificationClaim - -@verify_session( - # highlight-start - # We add the EmailVerificationClaim's is_verified validator - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \ - [EmailVerificationClaim.validators.is_verified()] - # highlight-end -) -async def like_comment(request: HttpRequest): - # All validator checks have passed and the user has a verified email address - pass -``` - - - - - - - -We add the SDK's `EmailVerificationClaim` validator to the `verifySession` middleware call as shown above, and that will only allow access if the email is verified, else it will return `403` to the frontend. - - - - - - - -## Step 4: Protecting frontend routes - - - - -

      If using REQUIRED mode

      - -Wrapping your website routes using `` should enforce email verification. If the user's email is not verified, SuperTokens will automatically redirect the user to the email verification screen. - -

      If using OPTIONAL mode

      - -```tsx -import React from "react"; -import { SessionAuth, useSessionContext } from 'supertokens-auth-react/recipe/session'; -import { EmailVerificationClaim } from 'supertokens-auth-react/recipe/emailverification'; - -const VerifiedRoute = (props: React.PropsWithChildren) => { - return ( - - - {props.children} - - - ); -} - -function InvalidClaimHandler(props: React.PropsWithChildren) { - let sessionContext = useSessionContext(); - if (sessionContext.loading) { - return null; - } - - if (sessionContext.invalidClaims.some(i => i.id === EmailVerificationClaim.id)) { - // Alternatively you could redirect the user to the email verification screen to trigger the verification email - // Note: /auth/verify-email is the default email verification path - // window.location.assign("/auth/verify-email") - return
      You cannot access this page because your email address is not verified.
      - } - - // We show the protected route since all claims validators have - // passed implying that the user has verified their email. - return
      {props.children}
      ; -} -``` -Above we are creating a generic component called `VerifiedRoute` which enforces that its child components can only be rendered if the user has a verified email address. - -In the `VerifiedRoute` component, we use the `SessionAuth` wrapper to ensure that the session exists. The `SessionAuth` wrapper will create a context that contains a prop called `invalidClaims` which will contain a list of all claim validations that have failed. - -The email verification recipe on the frontend, adds the `EmailVerificationClaim` validator automatically, so if the user's email is not verified, the `invalidClaims` prop will contain information about that. Alternatively you could also redirect the user to the default email verification path to trigger the sending of the verification email. - -We check the result of the validation in the `InvalidClaimHandler` component which displays `"You cannot access this page because your email address is not verified."` if the `EmailVerificationClaim` validator failed. - -If all validations pass, we render the `props.children` component. - -
      - - - -```tsx -import Session from "supertokens-web-js/recipe/session"; -import { EmailVerificationClaim } from "supertokens-web-js/recipe/emailverification"; - -async function shouldLoadRoute(): Promise { - if (await Session.doesSessionExist()) { - // highlight-start - let validationErrors = await Session.validateClaims(); - - if (validationErrors.length === 0) { - // user has verified their email address - return true; - } else { - for (const err of validationErrors) { - if (err.id === EmailVerificationClaim.id) { - // email is not verified - } - } - } - // highlight-end - } - // a session does not exist, or email is not verified - return false -} -``` - -In your protected routes, you need to first check if a session exists, and then call the `Session.validateClaims` function as shown above. This function inspects the session's contents and runs claim validators on them. If a claim validator fails, it will be reflected in the `validationErrors` variable. The `EmailVerificationClaim` validator will be automatically checked by this function since you have initialized the email verification recipe. - - - -
      - - - - - - - -## See also - -- [Post email verification action](../common-customizations/email-verification/handling-email-verification-success) -- [Change email verification link's lifetime](../common-customizations/email-verification/changing-token-lifetime) -- [Customise email template or email delivery method](../email-delivery/about) -- [Manually changing email verification status for a user](../common-customizations/email-verification/changing-email-verification-status) -- [Generating email verification links manually](../common-customizations/email-verification/generate-link-manually) -- [Replacing, customising or embedding the frontend UI](../common-customizations/email-verification/embed-in-page) -- 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. - - diff --git a/v2/thirdpartyemailpassword/pre-built-ui/handling-session-tokens.mdx b/v2/thirdpartyemailpassword/pre-built-ui/handling-session-tokens.mdx index 69b80421f..6ff934e75 100644 --- a/v2/thirdpartyemailpassword/pre-built-ui/handling-session-tokens.mdx +++ b/v2/thirdpartyemailpassword/pre-built-ui/handling-session-tokens.mdx @@ -4,165 +4,7 @@ title: Handling session tokens hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -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'; + -# Handling session tokens - - - - - -:::success -No action required. -::: - -Our frontend SDK handles everything for you. You only need to make sure that you have called `supertokens.init` before making any network requests. - -Our SDK adds interceptors to `fetch` and `XHR` (used by `axios`) to save and add session tokens from and to the request. - -By default, our web SDKs use cookies to provide credentials. - - - - - -## Getting the access token - -:::caution -Our SDK automatically handles adding the access token to request headers. You only need to add the access token to the request if you want to send the access token to a different API domain than what is configured on the frontend SDK init function call. -::: - -If you are using a header-based session or enabled `exposeAccessTokenToFrontendInCookieBasedAuth` (see below), you can read the access token on the frontend using the `getAccessToken` method: - - - - - - - - -```tsx -import Session from "supertokens-auth-react/recipe/session"; - -async function getToken(): Promise { - // highlight-next-line - const accessToken = await Session.getAccessToken(); - console.log(accessToken); -} -``` - - - - - -```tsx -import Session from "supertokens-web-js/recipe/session"; - -async function getToken(): Promise { - // highlight-next-line - const accessToken = await Session.getAccessToken(); - console.log(accessToken); -} -``` - - - - - - - -### If using cookie-based sessions - -:::caution -This will expose the access token to the frontend, meaning it could be vulnerable to XSS attacks. -::: - -:::important -If you are using header-based sessions, you can skip this step -::: - -By enabling this setting, you'll expose the access token to your frontend code even if you use cookies for authentication. - - - - - - - - -```tsx -import SuperTokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; - -SuperTokens.init({ - supertokens: { - connectionURI: "..." - }, - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "..." - }, - recipeList: [ - Session.init({ - //highlight-start - exposeAccessTokenToFrontendInCookieBasedAuth: true, - //highlight-end - }) - ] -}); -``` - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - supertokens.Init(supertokens.TypeInput{ - RecipeList: []supertokens.Recipe{ - session.Init(&sessmodels.TypeInput{ - //highlight-start - ExposeAccessTokenToFrontendInCookieBasedAuth: true, - //highlight-end - }), - }, - }) -} -``` - - - -```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import session - -init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore - recipe_list=[ - session.init( - # highlight-next-line - expose_access_token_to_frontend_in_cookie_based_auth=True - ) - ] -) -``` - - - - - diff --git a/v2/thirdpartyemailpassword/pre-built-ui/multitenant-login.mdx b/v2/thirdpartyemailpassword/pre-built-ui/multitenant-login.mdx index 9f508a27a..bf2091f6c 100644 --- a/v2/thirdpartyemailpassword/pre-built-ui/multitenant-login.mdx +++ b/v2/thirdpartyemailpassword/pre-built-ui/multitenant-login.mdx @@ -4,426 +4,8 @@ title: "Multitenant and Enterprise SSO login" hide_title: true --- -import AppInfoForm from "/src/components/appInfoForm" -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import PythonSyncAsyncSubTabs from "/src/components/tabs/PythonSyncAsyncSubTabs"; -import CoreVersionSubTabs from "/src/components/tabs/CoreVersionSubTabs"; -import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs"; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import MultiTenancyPaidBanner from '../../community/reusableMD/multitenancy/MultiTenancyPaidBanner.mdx' -import CoreInjector from "/src/components/coreInjector" -import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" +import Redirector from '/src/components/Redirector'; - - - - + -# Multitenant and Enterprise SSO login - -Multitenant login is a feature that lets you customize the login experience for each of your customers. For example, a customer `customer1` hosted on `customer1.yourdomain.com` can have login with `Active Directory` and `Google`, and another customer `customer2` hosted on `customer2.yourdomain.com` can have login with `Okta`, `Facebook` and email password based login. - -This is also the page that you should see if you want to implement sign in with: -- Okta (`thirdPartyId: "okta"`) -- SAML (`thirdPartyId: "boxy-saml"`) -- Active Directory (`thirdPartyId: "active-directory"`) -- Google Workspaces (`thirdPartyId: "google-workspaces"`) -- GitLab (`thirdPartyId: "gitlab"`) -- Bitbucket (`thirdPartyId: "bitbucket"`) -- Or any other workforce IdP - - - - - - - -## Step 1: Create and configure a new tenant in SuperTokens core - -Each tenant can be configured with a unique `tenantId`, and the list of third party connections (Active Directory, Google etc..) that should be allowed for them. - -You can create a new tenant using our backend SDKs or via a `cURL` command to the core. - - - - - - - - - - - -```tsx -import Multitenancy from "supertokens-node/recipe/multitenancy"; - -async function createNewTenant() { - - // highlight-start - let resp = await Multitenancy.createOrUpdateTenant("customer1", { - firstFactors: ["emailpassword", "thirdparty"] - }); - // highlight-end - - if (resp.createdNew) { - // new tenant was created - } else { - // existing tenant's config was modified. - } -} -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/multitenancy" - "github.com/supertokens/supertokens-golang/recipe/multitenancy/multitenancymodels" -) - -func main() { - tenantId := "customer1" - thirdPartyEnabled := true - emailPasswordEnabled := true - - // highlight-start - resp, err := multitenancy.CreateOrUpdateTenant(tenantId, multitenancymodels.TenantConfig{ - ThirdPartyEnabled: &thirdPartyEnabled, - EmailPasswordEnabled: &emailPasswordEnabled, - }) - // highlight-end - - if err != nil { - // handle error - } - if resp.OK.CreatedNew { - // new tenant was created - } else { - // existing tenant's config was modified. - } -} -``` - - - - - - - -```python -from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate - - -async def update_tenant(): - result = await create_or_update_tenant( - "customer1", - config=TenantConfigCreateOrUpdate( - first_factors=["thirdparty", "emailpassword"], - ), - ) - - if result.status != "OK": - print("Error creating or updating tenant") - elif result.created_new: - print("New tenant was created") - else: - print("Existing tenant's config was modified") -``` - - - - - -```python -from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate - - -def update_tenant(): - result = create_or_update_tenant( - "customer1", - config=TenantConfigCreateOrUpdate( - first_factors=["thirdparty", "emailpassword"], - ), - ) - - if result.status != "OK": - print("Error creating or updating tenant") - elif result.created_new: - print("New tenant was created") - else: - print("Existing tenant's config was modified") -``` - - - - - - - - - - - - - - -```bash -curl --location --request PUT '^{coreInjector_uri_without_quotes}/recipe/multitenancy/tenant/v2' \ ---header 'api-key: ^{coreInjector_api_key_without_quotes}' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "tenantId": "customer1", - "firstFactors": ["emailpassword", "thirdparty"] -}' -``` - - - - - -```bash -curl --location --request PUT '^{coreInjector_uri_without_quotes}/recipe/multitenancy/tenant' \ ---header 'api-key: ^{coreInjector_api_key_without_quotes}' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "tenantId": "customer1", - "emailPasswordEnabled": true, - "thirdPartyEnabled": true -}' -``` - - - - - - - - - - - -Create Tenant - - - - - - - - - - - -Once a tenant is created, add their thirdparty providers as shown below. - - - - - - - - - - - -```tsx -import Multiteancy from "supertokens-node/recipe/multitenancy"; - -async function createTenant() { - let resp = await Multiteancy.createOrUpdateThirdPartyConfig("customer1", { - thirdPartyId: "active-directory", - name: "Active Directory", - clients: [{ - clientId: "...", - clientSecret: "...", - }], - oidcDiscoveryEndpoint: "https://login.microsoftonline.com//v2.0/.well-known/openid-configuration", - }); - - if (resp.createdNew) { - // Active Directory added to customer1 - } else { - // Existing active directory config overwritten for customer1 - } -} -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/multitenancy" - "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" -) - -func main() { - tenantId := "customer1" - - // highlight-start - resp, err := multitenancy.CreateOrUpdateThirdPartyConfig(tenantId, tpmodels.ProviderConfig{ - ThirdPartyId: "active-directory", - Name: "Active Directory", - Clients: []tpmodels.ProviderClientConfig{ - { - ClientID: "...", - ClientSecret: "...", - }, - }, - OIDCDiscoveryEndpoint: "https://login.microsoftonline.com//v2.0/.well-known/openid-configuration", - }, nil, nil) - // highlight-end - - if err != nil { - // handle error - } - if resp.OK.CreatedNew { - // Active Directory added to customer1 - } else { - // Existing active directory config overwritten for customer1 - } -} -``` - - - - - - - -```python -from supertokens_python.recipe.multitenancy.asyncio import create_or_update_third_party_config -from supertokens_python.recipe.thirdparty.provider import ProviderConfig, ProviderClientConfig - -async def update_tenant(): - result = await create_or_update_third_party_config( - "customer1", - config=ProviderConfig( - third_party_id="active-directory", - name="Active Directory", - clients=[ - ProviderClientConfig( - client_id="...", - client_secret="...", - ) - ], - oidc_discovery_endpoint="https://login.microsoftonline.com//v2.0/.well-known/openid-configuration", - ), - ) - - if result.status != "OK": - print("Error adding active directory to tenant") - elif result.created_new: - print("Active directory was added to the tenant") - else: - print("Existing tenant's active directory config was modified") -``` - - - - - -```python -from supertokens_python.recipe.multitenancy.syncio import create_or_update_third_party_config -from supertokens_python.recipe.thirdparty.provider import ProviderConfig, ProviderClientConfig - -result = create_or_update_third_party_config( - "customer1", - config=ProviderConfig( - third_party_id="active-directory", - name="Active Directory", - clients=[ - ProviderClientConfig( - client_id="...", - client_secret="...", - ) - ] - ), - ) - -if result.status != "OK": - print("Error creating or updating tenant") -elif result.created_new: - print("New tenant was created") -else: - print("Existing tenant's config was modified") -``` - - - - - - - - - - -```bash -curl --location --request PUT '^{coreInjector_uri_without_quotes}//recipe/multitenancy/config/thirdparty' \ ---header 'api-key: ^{coreInjector_api_key_without_quotes}' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "config": { - "thirdPartyId": "active-directory", - "name": "Active Directory", - "clients": [ - { - "clientId": "...", - "clientSecret": "..." - } - ], - "oidcDiscoveryEndpoint": "https://login.microsoftonline.com//v2.0/.well-known/openid-configuration" - } -}' -``` - - - - - - - -Create Third Party Provider - - - - - - - - - - - -:::important -The above shows how to add an Active Directory config for the `customer1` tenant. You can see the config structure for all the in built providers [on this page](../common-customizations/sign-in-and-up/provider-config). -::: - - - - - - - -## Step 2: Build your multi tenant a UX flow - -The most common multi tenant flows are: -- [Tenants use a common domain to login](../common-customizations/multi-tenancy/common-domain-login): All tenants login using the same page (for example, `example.com/auth`) and are optionally redirected to their sub domain post login. At the start of the login flow, the customer will have to input their tenantId / workspace URL / identifier - as defined by you, and the login methods shown would be based on their tenantId. -- [Tenants use their sub domain to login](../common-customizations/multi-tenancy/sub-domain-login): Here, each tenant has a sub domain assigned to them (for example `customer1.example.com`, `customer2.example.com`, ...), and they would visit their sub domain to login and access their app. Each sub domain's login experience may be different (as defined by you or the tenant). - -SuperTokens is flexible enough to allow other forms of UX as well, but since the above two flow are most common, we provide dedicated docs for them (see the links above). - -## See also - -- Multi tenant [single domain](../common-customizations/multi-tenancy/common-domain-login) or [sub domain](../common-customizations/multi-tenancy/sub-domain-login) login. -- [See all built in multitenant providers](../common-customizations/sign-in-and-up/provider-config). -- [See how to add custom multitenant provider](../common-customizations/multi-tenancy/custom-provider). -- [SAML login](../common-customizations/saml/what-is-saml). -- [Tenant data isolation](../common-customizations/multi-tenancy/new-tenant-config#providing-additional-configuration-per-tenant). - - diff --git a/v2/thirdpartyemailpassword/pre-built-ui/securing-routes.mdx b/v2/thirdpartyemailpassword/pre-built-ui/securing-routes.mdx index a45ea028c..adf124a95 100644 --- a/v2/thirdpartyemailpassword/pre-built-ui/securing-routes.mdx +++ b/v2/thirdpartyemailpassword/pre-built-ui/securing-routes.mdx @@ -4,530 +4,8 @@ title: "Securing your API and frontend routes" hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -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 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 + -## Protecting APIs - - - - - - - -### Requiring an active session - -For your APIs that require a user to be logged in, use the `verifySession` middleware - - - - - - -```tsx -import express from "express"; -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import { SessionRequest } from "supertokens-node/framework/express"; - -let app = express(); - -// highlight-start -app.post("/like-comment", verifySession(), (req: SessionRequest, res) => { - let userId = req.session!.getUserId(); - // highlight-end - //.... -}); -``` - - - - -```tsx -import Hapi from "@hapi/hapi"; -import { verifySession } from "supertokens-node/recipe/session/framework/hapi"; -import { SessionRequest } from "supertokens-node/framework/hapi"; - -let server = Hapi.server({ port: 8000 }); - -server.route({ - path: "/like-comment", - method: "post", - //highlight-start - options: { - pre: [ - { - method: verifySession() - }, - ], - }, - handler: async (req: SessionRequest, res) => { - let userId = req.session!.getUserId(); - //highlight-end - //... - } -}) -``` - - - -```tsx -import Fastify from "fastify"; -import { verifySession } from "supertokens-node/recipe/session/framework/fastify"; -import { SessionRequest } from "supertokens-node/framework/fastify"; - -let fastify = Fastify(); - -//highlight-start -fastify.post("/like-comment", { - preHandler: verifySession(), -}, (req: SessionRequest, res) => { - let userId = req.session!.getUserId(); - //highlight-end - //.... -}); -``` - - - - -```tsx -import { verifySession } from "supertokens-node/recipe/session/framework/awsLambda"; -import { SessionEventV2 } from "supertokens-node/framework/awsLambda"; - -async function likeComment(awsEvent: SessionEventV2) { - let userId = awsEvent.session!.getUserId(); - //.... -}; - -//highlight-next-line -exports.handler = verifySession(likeComment); -``` - - - - -```tsx -import KoaRouter from "koa-router"; -import { verifySession } from "supertokens-node/recipe/session/framework/koa"; -import { SessionContext } from "supertokens-node/framework/koa"; - -let router = new KoaRouter(); - -//highlight-start -router.post("/like-comment", verifySession(), (ctx: SessionContext, next) => { - let userId = ctx.session!.getUserId(); - //highlight-end - //.... -}); -``` - - - - -```tsx -import { inject, intercept } from "@loopback/core"; -import { RestBindings, MiddlewareContext, post, response } from "@loopback/rest"; -import { verifySession } from "supertokens-node/recipe/session/framework/loopback"; -import { SessionContext } from "supertokens-node/framework/loopback"; - -class LikeComment { - //highlight-start - constructor(@inject(RestBindings.Http.CONTEXT) private ctx: MiddlewareContext) { } - @post("/like-comment") - @intercept(verifySession()) - @response(200) - handler() { - let userId = (this.ctx as SessionContext).session!.getUserId(); - //highlight-end - //.... - } -} -``` - - - - -```tsx -import { superTokensNextWrapper } from 'supertokens-node/nextjs' -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import { SessionRequest } from "supertokens-node/framework/express"; - -// highlight-start -export default async function likeComment(req: SessionRequest, res: any) { - await superTokensNextWrapper( - async (next) => { - await verifySession()(req, res, next); - }, - req, - res - ) - - let userId = req.session!.getUserId(); - // highlight-end - //.... -} -``` - - - - -```tsx -import { NextResponse, NextRequest } from "next/server"; -import SuperTokens from "supertokens-node"; -import { withSession } from "supertokens-node/nextjs"; -// @ts-ignore -import { backendConfig } from "@/app/config/backend"; - -SuperTokens.init(backendConfig()); - -export function POST(request: NextRequest) { - return withSession(request, async (err, session) => { - if (err) { - return NextResponse.json(err, { status: 500 }); - } - const userId = session!.getUserId(); - //.... - return NextResponse.json({}); - }); -} -``` - - - - -```tsx -import { Controller, Post, UseGuards, Session } from "@nestjs/common"; -import { SessionContainer } from "supertokens-node/recipe/session"; -// @ts-ignore -import { AuthGuard } from './auth/auth.guard'; - -@Controller() -export class ExampleController { - @Post('example') - @UseGuards(new AuthGuard()) // For more information about this guard please read our NestJS guide. - async postExample(@Session() session: SessionContainer): Promise { - //highlight-start - let userId = session.getUserId(); - - //highlight-end - //.... - return true; - } -} -``` - - - - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/supertokens/supertokens-golang/recipe/session" -) - -func main() { - _ = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - // Wrap the API handler in session.VerifySession - session.VerifySession(nil, likeCommentAPI).ServeHTTP(rw, r) - }) -} - -func likeCommentAPI(w http.ResponseWriter, r *http.Request) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(r.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/gin-gonic/gin" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" -) - -func main() { - router := gin.New() - - // Wrap the API handler in session.VerifySession - router.POST("/likecomment", verifySession(nil), likeCommentAPI) -} - -// This is a function that wraps the supertokens verification function -// to work the gin -func verifySession(options *sessmodels.VerifySessionOptions) gin.HandlerFunc { - return func(c *gin.Context) { - session.VerifySession(options, func(rw http.ResponseWriter, r *http.Request) { - c.Request = c.Request.WithContext(r.Context()) - c.Next() - })(c.Writer, c.Request) - // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly - c.Abort() - } -} - -func likeCommentAPI(c *gin.Context) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(c.Request.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/go-chi/chi" - "github.com/supertokens/supertokens-golang/recipe/session" -) - -func main() { - r := chi.NewRouter() - - // Wrap the API handler in session.VerifySession - r.Post("/likecomment", session.VerifySession(nil, likeCommentAPI)) -} - -func likeCommentAPI(w http.ResponseWriter, r *http.Request) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(r.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/gorilla/mux" - "github.com/supertokens/supertokens-golang/recipe/session" -) - -func main() { - router := mux.NewRouter() - - // Wrap the API handler in session.VerifySession - router.HandleFunc("/likecomment", session.VerifySession(nil, likeCommentAPI)).Methods(http.MethodPost) -} - -func likeCommentAPI(w http.ResponseWriter, r *http.Request) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(r.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - - - - - - -```python -from supertokens_python.recipe.session.framework.fastapi import verify_session -from supertokens_python.recipe.session import SessionContainer -from fastapi import Depends - -# highlight-start -@app.post('/like_comment') # type: ignore -async def like_comment(session: SessionContainer = Depends(verify_session())): - user_id = session.get_user_id() - # highlight-end - - print(user_id) -``` - - - - -```python -from supertokens_python.recipe.session.framework.flask import verify_session -from supertokens_python.recipe.session import SessionContainer -from flask import g - -# highlight-start -@app.route('/update-jwt', methods=['POST']) # type: ignore -@verify_session() -def like_comment(): - session: SessionContainer = g.supertokens # type: ignore - - user_id = session.get_user_id() - # highlight-end - - print(user_id) -``` - - - - -```python -from supertokens_python.recipe.session.framework.django.asyncio import verify_session -from django.http import HttpRequest -from supertokens_python.recipe.session import SessionContainer - -# highlight-start -@verify_session() -async def like_comment(request: HttpRequest): - session: SessionContainer = request.supertokens # type: ignore - - user_id = session.get_user_id() - # highlight-end - - print(user_id) -``` - - - - - - - -The `verifySession` function returns a `401` to the frontend if a session doesn't exist, or if the access token has expired, in which case, our frontend SDK automatically refreshes the session. - -In case of successful session verification, you get access to a `session` object using which you can get the user's ID, or manipulate the session information. - - - - - - - -### Microservice authentication - -For authentication between microservices on your backend, checkout the [microservice auth guide](/docs/microservice_auth/client-credentials). - - - -## Protecting website routes - - - - - -:::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. - -::: - - - - -You can wrap your components with the `` react component. This will ensure that your component renders only if the user is logged in. If they are not logged in, the user will be redirected to the login page. - -```tsx -import React from "react"; -import { - BrowserRouter, - Routes, - Route, -} from "react-router-dom"; -// highlight-next-line -import { SessionAuth } from "supertokens-auth-react/recipe/session"; -// @ts-ignore -import MyDashboardComponent from "./dashboard"; - -class App extends React.Component { - render() { - return ( - - - - {/*Components that require to be protected by authentication*/} - - - // highlight-end - } /> - - - ); - } -} -``` - - - - - -You can use the `doesSessionExist` function to check if a session exists in all your routes. - -```tsx -import Session from 'supertokens-web-js/recipe/session'; - -async function doesSessionExist() { - if (await Session.doesSessionExist()) { - // user is logged in - } else { - // user has not logged in yet - } -} -``` - - - - - - - - - - - -## See also - -- Optional sessions [for APIs](../common-customizations/sessions/session-verification-in-api/verify-session#optional-session-verification) and [the frontend](../common-customizations/sessions/checking-session-front-end) -- [Verifying session without using a middleware](../common-customizations/sessions/session-verification-in-api/get-session) -- Session claim validation [for APIs](../common-customizations/sessions/claims/claim-validators) and [the frontend](../common-customizations/sessions/claims/claim-validators) -- [Changing session lifetime](../common-customizations/sessions/change-session-timeout) -- [Sharing session across sub domains](../common-customizations/sessions/share-sessions-across-sub-domains) - - diff --git a/v2/thirdpartyemailpassword/pre-built-ui/setup/backend.mdx b/v2/thirdpartyemailpassword/pre-built-ui/setup/backend.mdx index ab3ec4bd8..577fc5a32 100644 --- a/v2/thirdpartyemailpassword/pre-built-ui/setup/backend.mdx +++ b/v2/thirdpartyemailpassword/pre-built-ui/setup/backend.mdx @@ -4,1166 +4,8 @@ title: "Step 2: Backend" hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import TabItem from '@theme/TabItem'; -import NodeJSFrameworkSubTabsServerless from "/src/components/tabs/NodeJSFrameworkSubTabsServerless" -import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" -import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs" -import BackendSDKCasing from "/src/components/BackendSDKCasing" -import AppInfoForm from "/src/components/appInfoForm" -import CoreInjector from "/src/components/coreInjector" -import {Question, Answer}from "/src/components/question" -# Backend Integration + -## Supported frameworks - -import TechStackSupport from "../../../community/reusableMD/supported-tech-stacks-backend.mdx" - - - -## 1) Install - - - - -```bash -npm i -s supertokens-node -``` - - - - -```bash -go get github.com/supertokens/supertokens-golang -``` - - - - - -```bash -pip install supertokens-python -``` - - - - -## 2) Initialise SuperTokens - - - - - -Add the code below to your server's init file. - - - - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import EmailPassword from"supertokens-node/recipe/emailpassword"; -import ThirdParty from"supertokens-node/recipe/thirdparty"; - -supertokens.init({ - framework: "express", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/thirdpartyemailpassword/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - EmailPassword.init(), - ThirdParty.init({/*TODO: See next step*/}), - Session.init() // initializes session features - ] -}); -``` - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import EmailPassword from"supertokens-node/recipe/emailpassword"; -import ThirdParty from"supertokens-node/recipe/thirdparty"; - -supertokens.init({ - framework: "hapi", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - EmailPassword.init(), - ThirdParty.init({/*TODO: See next step*/}), - Session.init() // initializes session features - ] -}); -``` - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import EmailPassword from"supertokens-node/recipe/emailpassword"; -import ThirdParty from"supertokens-node/recipe/thirdparty"; - -supertokens.init({ - framework: "fastify", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - EmailPassword.init(), - ThirdParty.init({/*TODO: See next step*/}), - Session.init() // initializes session features - ] -}); -``` - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import EmailPassword from"supertokens-node/recipe/emailpassword"; -import ThirdParty from"supertokens-node/recipe/thirdparty"; - -supertokens.init({ - framework: "koa", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - EmailPassword.init(), - ThirdParty.init({/*TODO: See next step*/}), - Session.init() // initializes session features - ] -}); -``` - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import EmailPassword from"supertokens-node/recipe/emailpassword"; -import ThirdParty from"supertokens-node/recipe/thirdparty"; - -supertokens.init({ - framework: "loopback", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - EmailPassword.init(), - ThirdParty.init({/*TODO: See next step*/}), - Session.init() // initializes session features - ] -}); -``` - - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/emailpassword" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/thirdparty" - "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - apiBasePath := "^{form_apiBasePath}" - websiteBasePath := "^{form_websiteBasePath}" - err := supertokens.Init(supertokens.TypeInput{ - Supertokens: &supertokens.ConnectionInfo{ - ^{coreInjector_connection_uri_comment} - ConnectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}APIKey: ^{coreInjector_api_key}, - }, - AppInfo: supertokens.AppInfo{ - AppName: "^{form_appName}", - APIDomain: "^{form_apiDomain}", - WebsiteDomain: "^{form_websiteDomain}", - APIBasePath: &apiBasePath, - WebsiteBasePath: &websiteBasePath, - }, - RecipeList: []supertokens.Recipe{ - thirdparty.Init(&tpmodels.TypeInput{ /*TODO: See next step*/ }), - emailpassword.Init(nil), - session.Init(nil), // initializes session features - }, - }) - - if err != nil { - panic(err.Error()) - } -} -``` - - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig -from supertokens_python.recipe import thirdparty, emailpassword, session - -init( - app_info=InputAppInfo( - app_name="^{form_appName}", - api_domain="^{form_apiDomain}", - website_domain="^{form_websiteDomain}", - api_base_path="^{form_apiBasePath}", - website_base_path="^{form_websiteBasePath}" - ), - supertokens_config=SupertokensConfig( - ^{coreInjector_connection_uri_comment_with_hash} - connection_uri=^{coreInjector_uri} - ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} - ), - framework='fastapi', - recipe_list=[ - session.init(), # initializes session features - thirdparty.init( - # TODO: See next step - ), - emailpassword.init() - ], - mode='asgi' # use wsgi if you are running using gunicorn -) -``` - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig -from supertokens_python.recipe import thirdparty, emailpassword, session - -init( - app_info=InputAppInfo( - app_name="^{form_appName}", - api_domain="^{form_apiDomain}", - website_domain="^{form_websiteDomain}", - api_base_path="^{form_apiBasePath}", - website_base_path="^{form_websiteBasePath}" - ), - supertokens_config=SupertokensConfig( - ^{coreInjector_connection_uri_comment_with_hash} - connection_uri=^{coreInjector_uri} - ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} - ), - framework='flask', - recipe_list=[ - session.init(), # initializes session features - thirdparty.init( - # TODO: See next step - ), - emailpassword.init() - ] -) -``` - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig -from supertokens_python.recipe import thirdparty, emailpassword, session - -init( - app_info=InputAppInfo( - app_name="^{form_appName}", - api_domain="^{form_apiDomain}", - website_domain="^{form_websiteDomain}", - api_base_path="^{form_apiBasePath}", - website_base_path="^{form_websiteBasePath}" - ), - supertokens_config=SupertokensConfig( - ^{coreInjector_connection_uri_comment_with_hash} - connection_uri=^{coreInjector_uri} - ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} - ), - framework='django', - recipe_list=[ - session.init(), # initializes session features - thirdparty.init( - # TODO: See next step - ), - emailpassword.init() - ], - mode='asgi' # use wsgi if you are running django server in sync mode -) -``` - - - - - - - - - - -## 3) Initialise Social login providers - - - -Populate the `providers` array with the third party auth providers you want. - - - - -```tsx -import SuperTokens from "supertokens-node"; -import ThirdParty from "supertokens-node/recipe/thirdparty" -import EmailPassword from "supertokens-node/recipe/emailpassword" - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - EmailPassword.init(), - ThirdParty.init({ - //highlight-start - // We have provided you with development keys which you can use for testing. - // IMPORTANT: Please replace them with your own OAuth keys for production use. - signInAndUpFeature: { - providers: [{ - config: { - thirdPartyId: "google", - clients: [{ - clientId: "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", - clientSecret: "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" - }] - } - }, { - config: { - thirdPartyId: "github", - clients: [{ - clientId: "467101b197249757c71f", - clientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd" - }] - } - }, { - config: { - thirdPartyId: "apple", - clients: [{ - clientId: "4398792-io.supertokens.example.service", - additionalConfig: { - keyId: "7M48Y4RYDL", - privateKey: - "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----", - teamId: "YWQCXGJRJL", - } - }] - } - }], - } - //highlight-end - }), - // ... - ] -}); -``` - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/thirdparty" - "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" -) - -func main() { - - // Inside supertokens.Init -> RecipeList - thirdparty.Init(&tpmodels.TypeInput{ - // highlight-start - SignInAndUpFeature: tpmodels.TypeInputSignInAndUp{ - Providers: []tpmodels.ProviderInput{ - // We have provided you with development keys which you can use for testing. - // IMPORTANT: Please replace them with your own OAuth keys for production use. - { - Config: tpmodels.ProviderConfig{ - ThirdPartyId: "google", - Clients: []tpmodels.ProviderClientConfig{ - { - ClientID: "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", - ClientSecret: "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", - }, - }, - }, - }, - { - Config: tpmodels.ProviderConfig{ - ThirdPartyId: "github", - Clients: []tpmodels.ProviderClientConfig{ - { - ClientID: "467101b197249757c71f", - ClientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd", - }, - }, - }, - }, - { - Config: tpmodels.ProviderConfig{ - ThirdPartyId: "apple", - Clients: []tpmodels.ProviderClientConfig{ - { - ClientID: "4398792-io.supertokens.example.service", - AdditionalConfig: map[string]interface{}{ - "keyId": "7M48Y4RYDL", - "privateKey": "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----", - "teamId": "YWQCXGJRJL", - }, - }, - }, - }, - }, - }, - }, - // highlight-end - }) -} -``` - - - -```python -from supertokens_python.recipe.thirdparty.provider import ProviderInput, ProviderConfig, ProviderClientConfig -from supertokens_python.recipe import thirdparty - -# Inside init -thirdparty.init( - # highlight-start - sign_in_and_up_feature=thirdparty.SignInAndUpFeature( - providers=[ - # We have provided you with development keys which you can use for testing. - # IMPORTANT: Please replace them with your own OAuth keys for production use. - ProviderInput( - config=ProviderConfig( - third_party_id="google", - clients=[ - ProviderClientConfig( - client_id="1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", - client_secret="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", - ), - ], - ), - ), - ProviderInput( - config=ProviderConfig( - third_party_id="github", - clients=[ - ProviderClientConfig( - client_id='467101b197249757c71f', - client_secret='e97051221f4b6426e8fe8d51486396703012f5bd' - ), - ], - ), - ), - ProviderInput( - config=ProviderConfig( - third_party_id="apple", - clients=[ - ProviderClientConfig( - client_id="4398792-io.supertokens.example.service", - additional_config={ - "keyId": "7M48Y4RYDL", - "privateKey": "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----", - "teamId": "YWQCXGJRJL" - }, - ), - ], - ), - ), - ] - ) - # highlight-end -) -``` - - - - -**When you want to generate your own keys**, please refer to the corresponding documentation to get your client ids and client secrets for each of the below providers: - -
      -Google - -- Generate your client ID and secret by following the [docs here](https://support.google.com/cloud/answer/6158849?hl=en) -- Set the authorisation callback URL to `^{form_websiteDomain}^{form_websiteBasePathForCallbacks}/callback/google` - -
      - -
      -Github - -- Generate your client ID and secret by following the [docs here](https://docs.github.com/en/developers/apps/creating-an-oauth-app) -- Set the authorisation callback URL to `^{form_websiteDomain}^{form_websiteBasePathForCallbacks}/callback/github` - -
      - -
      -Apple - -- Generate your client ID and secret by following [this article](https://medium.com/identity-beyond-borders/how-to-configure-sign-in-with-apple-77c61e336003) -- Set the authorisation callback URL to `^{form_apiDomain}^{form_apiBasePathForCallbacks}/callback/apple`. Note that Apple doesn't allow `localhost` in the URL. So if you are in dev mode, you can use the dev keys we have provided above. - -
      - -:::important -You can find the list of built in providers [here](../../common-customizations/signup-form/built-in-providers). To add a provider that is not listed, you can follow our guide on [setting up custom providers](../../common-customizations/signup-form/custom-providers). -::: - -
      - - -## 4) Add the SuperTokens APIs & CORS setup - - - - - - - - -:::important -- Add the `middleware` BEFORE all your routes. -- Add the `cors` middleware BEFORE the SuperTokens middleware as shown below. -::: - -```tsx -import express from "express"; -import cors from "cors"; -import supertokens from "supertokens-node"; -import {middleware} from "supertokens-node/framework/express"; - -let app = express(); - -app.use(cors({ - // highlight-start - origin: "^{form_websiteDomain}", - allowedHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], - credentials: true, - // highlight-end -})); - -// IMPORTANT: CORS should be before the below line. -// highlight-next-line -app.use(middleware()); - -// ...your API routes -``` -This `middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): - -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. -- `POST ^{form_apiBasePath}/signup`: For signing up a user with email & password -- `POST ^{form_apiBasePath}/signin`: For signing in a user with email & password - - - - -Register the `plugin`. - -```tsx -import Hapi from "@hapi/hapi"; -import supertokens from "supertokens-node"; -import { plugin } from "supertokens-node/framework/hapi"; - -let server = Hapi.server({ - port: 8000, - routes: { - // highlight-start - cors: { - origin: ["^{form_websiteDomain}"], - additionalHeaders: [...supertokens.getAllCORSHeaders()], - credentials: true, - } - // highlight-end - } -}); - -(async () => { - // highlight-next-line - await server.register(plugin); - - await server.start(); -})(); - -// ...your API routes -``` -This `plugin` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)) as well take care of all the errors thrown by the Supertokens library: - -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. -- `POST ^{form_apiBasePath}/signup`: For signing up a user with email & password -- `POST ^{form_apiBasePath}/signin`: For signing in a user with email & password - - - - -Register the `plugin`. Also register [`@fastify/formbody`](https://github.com/fastify/fastify-formbody) plugin. - -```tsx -import cors from "@fastify/cors"; -import supertokens from "supertokens-node"; -import { plugin } from "supertokens-node/framework/fastify"; -import formDataPlugin from "@fastify/formbody"; - -import fastifyImport from "fastify"; - -let fastify = fastifyImport(); - -// ...other middlewares -// highlight-start -fastify.register(cors, { - origin: "^{form_websiteDomain}", - allowedHeaders: ['Content-Type', ...supertokens.getAllCORSHeaders()], - credentials: true, -}); -// highlight-end - -(async () => { - // highlight-next-line - await fastify.register(formDataPlugin); - // highlight-next-line - await fastify.register(plugin); - - await fastify.listen(8000); -})(); - -// ...your API routes -``` -This `plugin` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): - -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. -- `POST ^{form_apiBasePath}/signup`: For signing up a user with email & password -- `POST ^{form_apiBasePath}/signin`: For signing in a user with email & password - - - - -:::important -Add the `middleware` BEFORE all your routes. -::: - -```tsx -import cors from '@koa/cors'; -import Koa from "koa"; -import supertokens from "supertokens-node"; -import { middleware } from "supertokens-node/framework/koa"; - -let app = new Koa(); - -// ...other middlewares -app.use(cors({ - // highlight-start - origin: "^{form_websiteDomain}", - allowHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], - credentials: true, - // highlight-end -})); - -// highlight-next-line -app.use(middleware()); - -// ...your API routes -``` -This `middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): - -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. -- `POST ^{form_apiBasePath}/signup`: For signing up a user with email & password -- `POST ^{form_apiBasePath}/signin`: For signing in a user with email & password - - - - -:::important -Add the `middleware` BEFORE all your routes. -::: - -```tsx -import supertokens from "supertokens-node"; -import { middleware } from "supertokens-node/framework/loopback"; -import { RestApplication } from '@loopback/rest'; - -let app = new RestApplication({ - rest: { - cors: { - // highlight-start - origin: "^{form_websiteDomain}", - allowedHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], - credentials: true - // highlight-end - } - } -}); - -// highlight-next-line -app.middleware(middleware); - -// ...your API routes -``` -This `middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): - -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. -- `POST ^{form_apiBasePath}/signup`: For signing up a user with email & password -- `POST ^{form_apiBasePath}/signin`: For signing in a user with email & password - - - - - - - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "net/http" - "strings" - - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - //highlight-start - http.ListenAndServe("SERVER ADDRESS", corsMiddleware( - supertokens.Middleware(http.HandlerFunc(func(rw http.ResponseWriter, - //highlight-end - r *http.Request) { - // TODO: Handle your APIs.. - - })))) -} - -func corsMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(response http.ResponseWriter, r *http.Request) { - response.Header().Set("Access-Control-Allow-Origin", "^{form_websiteDomain}") - response.Header().Set("Access-Control-Allow-Credentials", "true") - if r.Method == "OPTIONS" { - // we add content-type + other headers used by SuperTokens - response.Header().Set("Access-Control-Allow-Headers", - strings.Join(append([]string{"Content-Type"}, - //highlight-start - supertokens.GetAllCORSHeaders()...), ",")) - //highlight-end - response.Header().Set("Access-Control-Allow-Methods", "*") - response.Write([]byte("")) - } else { - next.ServeHTTP(response, r) - } - }) -} -``` - - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "net/http" - - "github.com/gin-contrib/cors" - "github.com/gin-gonic/gin" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - router := gin.New() - - // CORS - router.Use(cors.New(cors.Config{ - AllowOrigins: []string{"^{form_websiteDomain}"}, - AllowMethods: []string{"GET", "POST", "DELETE", "PUT", "OPTIONS"}, - AllowHeaders: append([]string{"content-type"}, - // highlight-next-line - supertokens.GetAllCORSHeaders()...), - AllowCredentials: true, - })) - - // Adding the SuperTokens middleware - // highlight-start - router.Use(func(c *gin.Context) { - supertokens.Middleware(http.HandlerFunc( - func(rw http.ResponseWriter, r *http.Request) { - c.Next() - })).ServeHTTP(c.Writer, c.Request) - // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly - c.Abort() - }) - // highlight-end - - // Add APIs and start server -} -``` - - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "github.com/go-chi/chi" - "github.com/go-chi/cors" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - r := chi.NewRouter() - - // CORS - r.Use(cors.Handler(cors.Options{ - AllowedOrigins: []string{"^{form_websiteDomain}"}, - AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, - AllowedHeaders: append([]string{"Content-Type"}, - //highlight-next-line - supertokens.GetAllCORSHeaders()...), - AllowCredentials: true, - })) - - // SuperTokens Middleware - //highlight-next-line - r.Use(supertokens.Middleware) - - // Add APIs and start server -} -``` - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "net/http" - - "github.com/gorilla/handlers" - "github.com/gorilla/mux" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - // Add APIs - - router := mux.NewRouter() - - // Adding handlers.CORS(options)(supertokens.Middleware(router))) - //highlight-start - http.ListenAndServe("SERVER ADDRESS", handlers.CORS( - handlers.AllowedHeaders(append([]string{"Content-Type"}, - supertokens.GetAllCORSHeaders()...)), - handlers.AllowedMethods([]string{"GET", "POST", "PUT", "HEAD", "OPTIONS"}), - handlers.AllowedOrigins([]string{"^{form_websiteDomain}"}), - handlers.AllowCredentials(), - )(supertokens.Middleware(router))) - //highlight-end -} -``` - - - -This `Middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): - -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. -- `POST ^{form_apiBasePath}/signup`: For signing up a user with email & password -- `POST ^{form_apiBasePath}/signin`: For signing in a user with email & password - - - - - - -Use the `Middleware` (**BEFORE all your routes**) and the `get_all_cors_headers()` functions as shown below. - -```python -from supertokens_python import get_all_cors_headers -from fastapi import FastAPI -from starlette.middleware.cors import CORSMiddleware -from supertokens_python.framework.fastapi import get_middleware - -app = FastAPI() -# highlight-next-line -app.add_middleware(get_middleware()) - -# TODO: Add APIs - -app.add_middleware( - CORSMiddleware, - allow_origins=[ - "^{form_websiteDomain}" - ], - allow_credentials=True, - allow_methods=["GET", "PUT", "POST", "DELETE", "OPTIONS", "PATCH"], - # highlight-next-line - allow_headers=["Content-Type"] + get_all_cors_headers(), -) - -# TODO: start server -``` - - - - -- Use the `Middleware` (**BEFORE all your routes and after calling init function**) and the `get_all_cors_headers()` functions as shown below. -- Add a route to catch all paths and return a 404. This is needed because if we don't add this, then OPTIONS request for the APIs exposed by the `Middleware` will return a `404`. - -```python -from supertokens_python import get_all_cors_headers -from flask import Flask, abort -from flask_cors import CORS # type: ignore -from supertokens_python.framework.flask import Middleware - -app = Flask(__name__) -# highlight-next-line -Middleware(app) - -# TODO: Add APIs - -CORS( - app=app, - origins=[ - "^{form_websiteDomain}" - ], - supports_credentials=True, - # highlight-next-line - allow_headers=["Content-Type"] + get_all_cors_headers(), -) - -# This is required since if this is not there, then OPTIONS requests for -# the APIs exposed by the supertokens' Middleware will return a 404 -# highlight-start -@app.route('/', defaults={'u_path': ''}) # type: ignore -@app.route('/') # type: ignore -def catch_all(u_path: str): - abort(404) -# highlight-end - -# TODO: start server -``` - - - - -Use the `Middleware` and the `get_all_cors_headers()` functions as shown below in your `settings.py`. - -```python -from supertokens_python import get_all_cors_headers -from typing import List -from corsheaders.defaults import default_headers - -CORS_ORIGIN_WHITELIST = [ - "^{form_websiteDomain}" -] - -CORS_ALLOW_CREDENTIALS = True - -CORS_ALLOWED_ORIGINS = [ - "^{form_websiteDomain}" -] - -CORS_ALLOW_HEADERS: List[str] = list(default_headers) + [ - "Content-Type" - # highlight-next-line -] + get_all_cors_headers() - -INSTALLED_APPS = [ - 'corsheaders', - 'supertokens_python' -] - -MIDDLEWARE = [ - 'corsheaders.middleware.CorsMiddleware', - ..., - # highlight-next-line - 'supertokens_python.framework.django.django_middleware.middleware', -] -``` - - - - -This `Middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): - -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. -- `POST ^{form_apiBasePath}/signup`: For signing up a user with email & password -- `POST ^{form_apiBasePath}/signin`: For signing in a user with email & password - - - - - - -## 5) Add the SuperTokens error handler - - - - - - - -```tsx -import { errorHandler } from "supertokens-node/framework/express"; -import express from "express"; -let app = express(); -// ...your API routes - -// highlight-start -// Add this AFTER all your routes -app.use(errorHandler()) -// highlight-end - -// your own error handler -app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => { - // Your error handler logic -}); -``` - - - -No additional `errorHandler` is required. - - - - -Add the `errorHandler` **Before all your routes and plugin registration** - -```tsx -import { errorHandler } from "supertokens-node/framework/fastify"; -import fastify from 'fastify' -const server = fastify() - -// highlight-next-line -server.setErrorHandler(errorHandler()); - -// ...your API routes - -``` - - - -No additional `errorHandler` is required. - - - - -No additional `errorHandler` is required. - - - - - - - -:::info -You can skip this step -::: - - - - -:::info -You can skip this step -::: - - - - -## 6) Setup the SuperTokens core - -You need to now setup an instance of the SuperTokens core for your app (that your backend should connect to). You have two options: -- [Managed service](./core/saas-setup) -- Self hosted with your own database ([With Docker](./core/with-docker) or [Without Docker](./core/without-docker)) diff --git a/v2/thirdpartyemailpassword/pre-built-ui/setup/frontend.mdx b/v2/thirdpartyemailpassword/pre-built-ui/setup/frontend.mdx index 03e0bc0f9..bac6ce88b 100644 --- a/v2/thirdpartyemailpassword/pre-built-ui/setup/frontend.mdx +++ b/v2/thirdpartyemailpassword/pre-built-ui/setup/frontend.mdx @@ -3,687 +3,9 @@ id: frontend title: "Step 1: Frontend" hide_title: true --- - - - -import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" -import TabItem from '@theme/TabItem'; -import {Question, Answer}from "/src/components/question" -import AppInfoForm from "/src/components/appInfoForm" -import RRDVersionSubTabs from "/src/components/tabs/RRDVersionSubTabs" -import NpmVersionOrYarnSubTabs from "/src/components/tabs/NpmVersionOrYarnSubTabs" -import PreBuiltUIInjector from "/src/components/prebuiltuiInjector" +import Redirector from '/src/components/Redirector'; -# Frontend Integration -## Supported frameworks + -import TechStackSupport from "../../../community/reusableMD/supported-tech-stacks-frontend.mdx" - - - -# Automatic setup using CLI - -Run the following command in your terminal. -```bash -npx create-supertokens-app@latest --recipe=^{docsLinkRecipeName} -``` - -Once this is done, you can skip Step (1) and (2) in this section (see left nav bar) and move directly to setting up the SuperTokens core (Step 3). - -Or, you can manually integrate SuperTokens by following the steps below. - -# Manual setup steps below - -## 1) Install - - - - - - - - -```bash -npm i -s supertokens-auth-react -``` - - - - -```bash -npm i -s supertokens-auth-react supertokens-web-js -``` - - - - -```bash -yarn add supertokens-auth-react supertokens-web-js -``` - - - - - - - - - -Start by installing the SuperTokens Web SDK: - -```bash -npm i -s supertokens-web-js -``` - - - - - -Start by installing the SuperTokens Web SDK: - -```bash -npm i -s supertokens-web-js -``` - - - - - -Start by installing the SuperTokens Web SDK: - -```bash -yarn add supertokens-web-js -``` - - - - - - - - - - - -Start by installing the SuperTokens web SDK: - -```bash -npm i -s supertokens-web-js -``` - - - - - -Start by installing the SuperTokens web SDK: - -```bash -npm i -s supertokens-web-js -``` - - - - - -Start by installing the SuperTokens web SDK: - -```bash -yarn add supertokens-web-js -``` - - - - - - - - -:::important -SuperTokens does not support pre-built UI for mobile frameworks. Please refer to the [setup guide for custom UI](../../custom-ui/init/frontend) to integrate SuperTokens in your app. -::: - - - - - - - -## 2) Call the `init` function - - - - - - -```tsx -import React from 'react'; - -import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; -import ThirdParty, { Github, Google, Facebook, Apple } from "supertokens-auth-react/recipe/thirdparty"; -import EmailPassword from "supertokens-auth-react/recipe/emailpassword"; -import Session from "supertokens-auth-react/recipe/session"; - -// highlight-start -SuperTokens.init({ - appInfo: { - // learn more about this on https://supertokens.com/docs/thirdpartyemailpassword/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - ThirdParty.init({ - signInAndUpFeature: { - providers: [ - Github.init(), - Google.init(), - Facebook.init(), - Apple.init(), - ] - } - }), - EmailPassword.init(), - Session.init() - ] -}); -// highlight-end - - -/* Your App */ -class App extends React.Component { - render() { - return ( - // highlight-next-line - - {/*Your app components*/} - // highlight-next-line - - ); - } -} -``` - - - - - - - - - - - -Before we initialize the `supertokens-web-js` SDK let's see how we will use it in our Angular app - -**Architecture** - -- The `supertokens-web-js` SDK is responsible for session management and providing helper functions to check if a session exists, or validate the access token claims on the frontend (for example, to check for user roles before showing some UI). We will initialise this SDK on the root of your Angular app, so that all pages in your app can use it. -- We will create a `^{form_websiteBasePath}*` route in the Angular app which will render our pre built UI which will also need to be initialised, but only on that route. - -**Creating the `^{form_websiteBasePath}` route** - -- Use the Angular CLI to generate a new route - - ```bash - ng generate module auth --route auth --module app.module - ``` - -- Add the following code to your `auth` angular component - - ```tsx title="/app/auth/auth.component.ts" - import {init as supertokensUIInit} from "supertokens-auth-react-script"; - import supertokensUIEmailPassword from "supertokens-auth-react-script/recipe/emailpassword"; - import supertokensUIThirdParty from "supertokens-auth-react-script/recipe/thirdparty"; - import supertokensUISession from "supertokens-auth-react-script/recipe/session"; - 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: [ - supertokensUIEmailPassword.init(), - supertokensUIThirdParty.init({ - signInAndUpFeature: { - providers: [ - supertokensUIThirdParty.Github.init(), - supertokensUIThirdParty.Google.init(), - supertokensUIThirdParty.Facebook.init(), - supertokensUIThirdParty.Apple.init(), - ] - } - }), - supertokensUISession.init(), - ], - }); - } - this.renderer.appendChild(this.document.body, script); - } - } - ``` - - In the `loadScript` function, we provide the SuperTokens config for the UI. We add the emailpassword, session and social login recipes along with Github, Google, Facebook and Apple login buttons. - -- Initialize the `supertokens-web-js` SDK in your angular app's root component. This will provide session management across your entire application. - - ```tsx title="/app/app.component.ts " - import SuperTokens from "supertokens-web-js"; - import Session from "supertokens-web-js/recipe/session"; - - SuperTokens.init({ - appInfo: { - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}", - }, - recipeList: [ - Session.init(), - ], - }); - ``` - -
      - -
      - -
      - - - - - - - -Before we initialize the `supertokens-web-js` SDK let's see how we will use it in our Vue app - -**Architecture** - -- The `supertokens-web-js` SDK is responsible for session management and providing helper functions to check if a session exists, or validate the access token claims on the frontend (for example, to check for user roles before showing some UI). We will initialise this SDK on the root of your Vue app, so that all pages in your app can use it. -- We will create a `^{form_websiteBasePath}*` route in the Vue app which will render our pre built UI which will also need to be initialised, but only on that route. - -**Creating the `^{form_websiteBasePath}` route** - -- Create a new file `AuthView.vue`, this Vue component will be used to render the auth component: - ```tsx - import {init as supertokensUIInit} from "supertokens-auth-react-script"; - import supertokensUIThirdParty from "supertokens-auth-react-script/recipe/thirdparty"; - import supertokensUIEmailPassword from "supertokens-auth-react-script/recipe/emailpassword"; - import supertokensUISession from "supertokens-auth-react-script/recipe/session"; - - - - ``` - - In the `loadScript` function, we provide the SuperTokens config for the UI. We add the emailpassword, session and social login recipes along with Github, Google, Facebook and Apple login buttons. - -- Initialize the `supertokens-web-js` SDK in your Vue app's `main.ts` file. This will provide session management across your entire application. - - ```tsx title="/main.ts " - // @ts-ignore - import { createApp } from "vue"; - import SuperTokens from "supertokens-web-js"; - import Session from "supertokens-web-js/recipe/session"; - // @ts-ignore - import App from "./App.vue"; - // @ts-ignore - import router from "./router"; - - SuperTokens.init({ - appInfo: { - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}", - }, - recipeList: [ - Session.init(), - ], - }); - - const app = createApp(App); - - app.use(router); - - app.mount("#app"); - - ``` - - - - - - - - - -:::important -SuperTokens does not support pre-built UI for mobile frameworks. Please refer to the [setup guide for custom UI](../../custom-ui/init/frontend) to integrate SuperTokens in your app. -::: - - - -
      - - - - - - -## 3) Setup Routing to show the login UI - - - - - - - - - - - -Call the `getSuperTokensRoutesForReactRouterDom` method from within any `react-router-dom` `Routes` component. - -```tsx -import React from 'react'; -import { - BrowserRouter, - Routes, - Route, - Link -} from "react-router-dom"; - -// highlight-next-line -import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; -import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui"; -^{prebuiltuiimport} -import * as reactRouterDom from "react-router-dom"; - -class App extends React.Component { - render() { - return ( - - - - {/*This renders the login UI on the ^{form_websiteBasePath} route*/} - // highlight-next-line - {getSuperTokensRoutesForReactRouterDom(reactRouterDom, [^{recipePreBuiltUINameCapitalLetters}])} - {/*Your app routes*/} - - - - ); - } -} -``` - - - - - -Call the `getSuperTokensRoutesForReactRouterDom` method from within any `react-router-dom` `Switch` component. - -```tsx -import React from 'react'; -import { - BrowserRouter, - Switch, - Route, - Link -} from "react-router-dom5"; - -// highlight-next-line -import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; -import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui"; -^{prebuiltuiimport} -import * as reactRouterDom from "react-router-dom"; - - -class App extends React.Component { - render() { - return ( - - - - {/*This renders the login UI on the ^{form_websiteBasePath} route*/} - // highlight-next-line - {getSuperTokensRoutesForReactRouterDom(reactRouterDom, [^{recipePreBuiltUINameCapitalLetters}])} - {/*Your app routes*/} - - - - ); - } -} -``` - - - - - - - - -Add the highlighted code snippet to your root level `render` function. - -```tsx -import React from 'react'; -^{prebuiltuiimport} -import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; -import { canHandleRoute, getRoutingComponent } from "supertokens-auth-react/ui"; - -class App extends React.Component { - render() { - // highlight-start - if (canHandleRoute([^{recipePreBuiltUINameCapitalLetters}])) { - // This renders the login UI on the ^{form_websiteBasePath} route - return getRoutingComponent([^{recipePreBuiltUINameCapitalLetters}]) - } - // highlight-end - - return ( - {/*Your app*/} - ); - } - -} -``` - - - - - - - - - - -Update your angular router so that all auth related requests load the `auth` component - -```tsx title="/app/app-routing.module.ts" -import { NgModule } from "@angular/core"; -import { RouterModule, Routes } from "@angular/router"; - -const routes: Routes = [ - // highlight-start - { - path: "^{form_websiteBasePath_withoutForwardSlash}", - // @ts-ignore - loadChildren: () => import("./auth/auth.module").then((m) => m.AuthModule), - }, - - // @ts-ignore - { path: "**", loadChildren: () => import("./home/home.module").then((m) => m.HomeModule) }, - // highlight-end -]; - -@NgModule({ - imports: [RouterModule.forRoot(routes)], - exports: [RouterModule], -}) -export class AppRoutingModule {} -``` - - - - - - - - -Update your Vue router so that all auth related requests load the `AuthView` component - -```tsx title="/router/index.ts" -// @ts-ignore -import { createRouter, createWebHistory } from "vue-router"; -// @ts-ignore -import HomeView from "../views/HomeView.vue"; -// @ts-ignore -import AuthView from "../views/AuthView.vue"; - -const router = createRouter({ - // @ts-ignore - history: createWebHistory(import.meta.env.BASE_URL), - routes: [ - { - path: "/", - name: "home", - component: HomeView, - }, - { - path: "^{form_websiteBasePath}/:pathMatch(.*)*", - name: "auth", - component: AuthView, - }, - ], -}); - -export default router; -``` - - - - - - - -:::important -SuperTokens does not support pre-built UI for mobile frameworks. Please refer to the [setup guide for custom UI](../../custom-ui/init/frontend) to integrate SuperTokens in your app. -::: - - - - - - - -## 4) View the login UI - - -^{form_addVisitWebsiteBasePathText} - - - -At this stage, you've successfully integrated your frontend with SuperTokens. The next section will guide you through setting up your backend. - diff --git a/v2/thirdpartyemailpassword/quickstart/backend-setup.mdx b/v2/thirdpartyemailpassword/quickstart/backend-setup.mdx new file mode 100644 index 000000000..069112468 --- /dev/null +++ b/v2/thirdpartyemailpassword/quickstart/backend-setup.mdx @@ -0,0 +1,1505 @@ +--- +id: backend-setup +title: Backend Setup +hide_title: true +show_ui_switcher: false +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +import PreBuiltUIInjector from "/src/components/prebuiltuiInjector" +import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; +import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" +import {Question, Answer}from "/src/components/question" +import RRDVersionSubTabs from "/src/components/tabs/RRDVersionSubTabs" +import AppInfoForm from "/src/components/appInfoForm" +import NodeJSFrameworkSubTabsServerless from "/src/components/tabs/NodeJSFrameworkSubTabsServerless" +import CoreInjector from "/src/components/coreInjector" +import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs" +import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" +import BackendSDKCasing from "/src/components/BackendSDKCasing" + + + +# Backend Setup + +Let's got through the changes required so that your backend can expose the **SuperTokens** authentication features. + +## 1. Install the SDK + + + + + +Run the following command in your terminal to install the package. + + + + +```bash +npm i -s supertokens-node +``` + + + + +```bash +go get github.com/supertokens/supertokens-golang +``` + + + + +```bash +pip install supertokens-python +``` + + + + + + +## 2. Initialize the SDK + +You will have to intialize the **Backend SDK** alongside the code that starts your server. +The init call will include [configuration details](../appinfo) for your app, how the backend will connect to the **SuperTokens Core**, as well as the **Recipes** that will be used in your setup. + + + + + +Add the code below to your server's init file. + + + + + + + +```tsx +import supertokens from "supertokens-node"; +import Session from "supertokens-node/recipe/session"; +import EmailPassword from"supertokens-node/recipe/emailpassword"; +import ThirdParty from"supertokens-node/recipe/thirdparty"; + +supertokens.init({ + framework: "express", + supertokens: { + ^{coreInjector_connection_uri_comment} + connectionURI: ^{coreInjector_uri} + ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, + }, + appInfo: { + // learn more about this on https://supertokens.com/docs/thirdpartyemailpassword/appinfo + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + websiteDomain: "^{form_websiteDomain}", + apiBasePath: "^{form_apiBasePath}", + websiteBasePath: "^{form_websiteBasePath}" + }, + recipeList: [ + EmailPassword.init(), + ThirdParty.init({/*TODO: See next step*/}), + Session.init() // initializes session features + ] +}); +``` + + + + +```tsx +import supertokens from "supertokens-node"; +import Session from "supertokens-node/recipe/session"; +import EmailPassword from"supertokens-node/recipe/emailpassword"; +import ThirdParty from"supertokens-node/recipe/thirdparty"; + +supertokens.init({ + framework: "hapi", + supertokens: { + ^{coreInjector_connection_uri_comment} + connectionURI: ^{coreInjector_uri} + ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, + }, + appInfo: { + // learn more about this on https://supertokens.com/docs/session/appinfo + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + websiteDomain: "^{form_websiteDomain}", + apiBasePath: "^{form_apiBasePath}", + websiteBasePath: "^{form_websiteBasePath}" + }, + recipeList: [ + EmailPassword.init(), + ThirdParty.init({/*TODO: See next step*/}), + Session.init() // initializes session features + ] +}); +``` + + + + +```tsx +import supertokens from "supertokens-node"; +import Session from "supertokens-node/recipe/session"; +import EmailPassword from"supertokens-node/recipe/emailpassword"; +import ThirdParty from"supertokens-node/recipe/thirdparty"; + +supertokens.init({ + framework: "fastify", + supertokens: { + ^{coreInjector_connection_uri_comment} + connectionURI: ^{coreInjector_uri} + ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, + }, + appInfo: { + // learn more about this on https://supertokens.com/docs/session/appinfo + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + websiteDomain: "^{form_websiteDomain}", + apiBasePath: "^{form_apiBasePath}", + websiteBasePath: "^{form_websiteBasePath}" + }, + recipeList: [ + EmailPassword.init(), + ThirdParty.init({/*TODO: See next step*/}), + Session.init() // initializes session features + ] +}); +``` + + + + +```tsx +import supertokens from "supertokens-node"; +import Session from "supertokens-node/recipe/session"; +import EmailPassword from"supertokens-node/recipe/emailpassword"; +import ThirdParty from"supertokens-node/recipe/thirdparty"; + +supertokens.init({ + framework: "koa", + supertokens: { + ^{coreInjector_connection_uri_comment} + connectionURI: ^{coreInjector_uri} + ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, + }, + appInfo: { + // learn more about this on https://supertokens.com/docs/session/appinfo + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + websiteDomain: "^{form_websiteDomain}", + apiBasePath: "^{form_apiBasePath}", + websiteBasePath: "^{form_websiteBasePath}" + }, + recipeList: [ + EmailPassword.init(), + ThirdParty.init({/*TODO: See next step*/}), + Session.init() // initializes session features + ] +}); +``` + + + + +```tsx +import supertokens from "supertokens-node"; +import Session from "supertokens-node/recipe/session"; +import EmailPassword from"supertokens-node/recipe/emailpassword"; +import ThirdParty from"supertokens-node/recipe/thirdparty"; + +supertokens.init({ + framework: "loopback", + supertokens: { + ^{coreInjector_connection_uri_comment} + connectionURI: ^{coreInjector_uri} + ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, + }, + appInfo: { + // learn more about this on https://supertokens.com/docs/session/appinfo + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + websiteDomain: "^{form_websiteDomain}", + apiBasePath: "^{form_apiBasePath}", + websiteBasePath: "^{form_websiteBasePath}" + }, + recipeList: [ + EmailPassword.init(), + ThirdParty.init({/*TODO: See next step*/}), + Session.init() // initializes session features + ] +}); +``` + + + + + +```go +import ( + "github.com/supertokens/supertokens-golang/recipe/emailpassword" + "github.com/supertokens/supertokens-golang/recipe/session" + "github.com/supertokens/supertokens-golang/recipe/thirdparty" + "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" + "github.com/supertokens/supertokens-golang/supertokens" +) + +func main() { + apiBasePath := "^{form_apiBasePath}" + websiteBasePath := "^{form_websiteBasePath}" + err := supertokens.Init(supertokens.TypeInput{ + Supertokens: &supertokens.ConnectionInfo{ + ^{coreInjector_connection_uri_comment} + ConnectionURI: ^{coreInjector_uri} + ^{coreInjector_api_key_commented}APIKey: ^{coreInjector_api_key}, + }, + AppInfo: supertokens.AppInfo{ + AppName: "^{form_appName}", + APIDomain: "^{form_apiDomain}", + WebsiteDomain: "^{form_websiteDomain}", + APIBasePath: &apiBasePath, + WebsiteBasePath: &websiteBasePath, + }, + RecipeList: []supertokens.Recipe{ + thirdparty.Init(&tpmodels.TypeInput{ /*TODO: See next step*/ }), + emailpassword.Init(nil), + session.Init(nil), // initializes session features + }, + }) + + if err != nil { + panic(err.Error()) + } +} +``` + + + + + +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import thirdparty, emailpassword, session + +init( + app_info=InputAppInfo( + app_name="^{form_appName}", + api_domain="^{form_apiDomain}", + website_domain="^{form_websiteDomain}", + api_base_path="^{form_apiBasePath}", + website_base_path="^{form_websiteBasePath}" + ), + supertokens_config=SupertokensConfig( + ^{coreInjector_connection_uri_comment_with_hash} + connection_uri=^{coreInjector_uri} + ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} + ), + framework='fastapi', + recipe_list=[ + session.init(), # initializes session features + thirdparty.init( + # TODO: See next step + ), + emailpassword.init() + ], + mode='asgi' # use wsgi if you are running using gunicorn +) +``` + + + + +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import thirdparty, emailpassword, session + +init( + app_info=InputAppInfo( + app_name="^{form_appName}", + api_domain="^{form_apiDomain}", + website_domain="^{form_websiteDomain}", + api_base_path="^{form_apiBasePath}", + website_base_path="^{form_websiteBasePath}" + ), + supertokens_config=SupertokensConfig( + ^{coreInjector_connection_uri_comment_with_hash} + connection_uri=^{coreInjector_uri} + ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} + ), + framework='flask', + recipe_list=[ + session.init(), # initializes session features + thirdparty.init( + # TODO: See next step + ), + emailpassword.init() + ] +) +``` + + + + +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import thirdparty, emailpassword, session + +init( + app_info=InputAppInfo( + app_name="^{form_appName}", + api_domain="^{form_apiDomain}", + website_domain="^{form_websiteDomain}", + api_base_path="^{form_apiBasePath}", + website_base_path="^{form_websiteBasePath}" + ), + supertokens_config=SupertokensConfig( + ^{coreInjector_connection_uri_comment_with_hash} + connection_uri=^{coreInjector_uri} + ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} + ), + framework='django', + recipe_list=[ + session.init(), # initializes session features + thirdparty.init( + # TODO: See next step + ), + emailpassword.init() + ], + mode='asgi' # use wsgi if you are running django server in sync mode +) +``` + + + + + + + + + + +## 3. Initialise Social Login Providers + + + +Populate the `providers` array with the third party auth providers you want. + + + + +```tsx +import SuperTokens from "supertokens-node"; +import ThirdParty from "supertokens-node/recipe/thirdparty" +import EmailPassword from "supertokens-node/recipe/emailpassword" + +SuperTokens.init({ + appInfo: { + apiDomain: "...", + appName: "...", + websiteDomain: "...", + }, + recipeList: [ + EmailPassword.init(), + ThirdParty.init({ + //highlight-start + // We have provided you with development keys which you can use for testing. + // IMPORTANT: Please replace them with your own OAuth keys for production use. + signInAndUpFeature: { + providers: [{ + config: { + thirdPartyId: "google", + clients: [{ + clientId: "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", + clientSecret: "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" + }] + } + }, { + config: { + thirdPartyId: "github", + clients: [{ + clientId: "467101b197249757c71f", + clientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd" + }] + } + }, { + config: { + thirdPartyId: "apple", + clients: [{ + clientId: "4398792-io.supertokens.example.service", + additionalConfig: { + keyId: "7M48Y4RYDL", + privateKey: + "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----", + teamId: "YWQCXGJRJL", + } + }] + } + }], + } + //highlight-end + }), + // ... + ] +}); +``` + + + +```go +import ( + "github.com/supertokens/supertokens-golang/recipe/thirdparty" + "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" +) + +func main() { + + // Inside supertokens.Init -> RecipeList + thirdparty.Init(&tpmodels.TypeInput{ + // highlight-start + SignInAndUpFeature: tpmodels.TypeInputSignInAndUp{ + Providers: []tpmodels.ProviderInput{ + // We have provided you with development keys which you can use for testing. + // IMPORTANT: Please replace them with your own OAuth keys for production use. + { + Config: tpmodels.ProviderConfig{ + ThirdPartyId: "google", + Clients: []tpmodels.ProviderClientConfig{ + { + ClientID: "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", + ClientSecret: "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", + }, + }, + }, + }, + { + Config: tpmodels.ProviderConfig{ + ThirdPartyId: "github", + Clients: []tpmodels.ProviderClientConfig{ + { + ClientID: "467101b197249757c71f", + ClientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd", + }, + }, + }, + }, + { + Config: tpmodels.ProviderConfig{ + ThirdPartyId: "apple", + Clients: []tpmodels.ProviderClientConfig{ + { + ClientID: "4398792-io.supertokens.example.service", + AdditionalConfig: map[string]interface{}{ + "keyId": "7M48Y4RYDL", + "privateKey": "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----", + "teamId": "YWQCXGJRJL", + }, + }, + }, + }, + }, + }, + }, + // highlight-end + }) +} +``` + + + +```python +from supertokens_python.recipe.thirdparty.provider import ProviderInput, ProviderConfig, ProviderClientConfig +from supertokens_python.recipe import thirdparty + +# Inside init +thirdparty.init( + # highlight-start + sign_in_and_up_feature=thirdparty.SignInAndUpFeature( + providers=[ + # We have provided you with development keys which you can use for testing. + # IMPORTANT: Please replace them with your own OAuth keys for production use. + ProviderInput( + config=ProviderConfig( + third_party_id="google", + clients=[ + ProviderClientConfig( + client_id="1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", + client_secret="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", + ), + ], + ), + ), + ProviderInput( + config=ProviderConfig( + third_party_id="github", + clients=[ + ProviderClientConfig( + client_id='467101b197249757c71f', + client_secret='e97051221f4b6426e8fe8d51486396703012f5bd' + ), + ], + ), + ), + ProviderInput( + config=ProviderConfig( + third_party_id="apple", + clients=[ + ProviderClientConfig( + client_id="4398792-io.supertokens.example.service", + additional_config={ + "keyId": "7M48Y4RYDL", + "privateKey": "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----", + "teamId": "YWQCXGJRJL" + }, + ), + ], + ), + ), + ] + ) + # highlight-end +) +``` + + + + +**When you want to generate your own keys**, please refer to the corresponding documentation to get your client ids and client secrets for each of the below providers: + +
      +Google + +- Generate your client ID and secret by following the [docs here](https://support.google.com/cloud/answer/6158849?hl=en) +- Set the authorisation callback URL to `^{form_websiteDomain}^{form_websiteBasePathForCallbacks}/callback/google` + +
      + +
      +Github + +- Generate your client ID and secret by following the [docs here](https://docs.github.com/en/developers/apps/creating-an-oauth-app) +- Set the authorisation callback URL to `^{form_websiteDomain}^{form_websiteBasePathForCallbacks}/callback/github` + +
      + +
      +Apple + +- Generate your client ID and secret by following [this article](https://medium.com/identity-beyond-borders/how-to-configure-sign-in-with-apple-77c61e336003) +- Set the authorisation callback URL to `^{form_apiDomain}^{form_apiBasePathForCallbacks}/callback/apple`. Note that Apple doesn't allow `localhost` in the URL. So if you are in dev mode, you can use the dev keys we have provided above. + +
      + +:::important + +You can find the list of built in providers [here](../common-customizations/signup-form/built-in-providers). To add a provider that is not listed, you can follow our guide on [setting up custom providers](../common-customizations/signup-form/custom-providers). + +::: + +
      + +## 4. Add the SuperTokens APIs and Configure CORS + + + + + +Now that the SDK is initialized you need to expose the endpoints that will be used by the frontend SDKs. +Besides this, your server's CORS, Cross-Origin Resource Sharing, settings should be updated to allow the use of the authentication headers required by **SuperTokens**. + + + + + + + + +:::important +- Add the `middleware` BEFORE all your routes. +- Add the `cors` middleware BEFORE the SuperTokens middleware as shown below. +::: + +```tsx +import express from "express"; +import cors from "cors"; +import supertokens from "supertokens-node"; +import { middleware } from "supertokens-node/framework/express"; + +let app = express(); + +app.use(cors({ + // highlight-start + origin: "^{form_websiteDomain}", + allowedHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], + credentials: true, + // highlight-end +})); + +// IMPORTANT: CORS should be before the below line. +// highlight-next-line +app.use(middleware()); + +// ...your API routes +``` + + + + +Register the `plugin`. + +```tsx +import Hapi from "@hapi/hapi"; +import supertokens from "supertokens-node"; +import { plugin } from "supertokens-node/framework/hapi"; + +let server = Hapi.server({ + port: 8000, + routes: { + // highlight-start + cors: { + origin: ["^{form_websiteDomain}"], + additionalHeaders: [...supertokens.getAllCORSHeaders()], + credentials: true, + } + // highlight-end + } +}); + +(async () => { + // highlight-next-line + await server.register(plugin); + + await server.start(); +})(); + +// ...your API routes +``` + + + + +Register the `plugin`. Also register [`@fastify/formbody`](https://github.com/fastify/fastify-formbody) plugin. + +```tsx +import cors from "@fastify/cors"; +import supertokens from "supertokens-node"; +import { plugin } from "supertokens-node/framework/fastify"; +import formDataPlugin from "@fastify/formbody"; + +import fastifyImport from "fastify"; + +let fastify = fastifyImport(); + +// ...other middlewares +// highlight-start +fastify.register(cors, { + origin: "^{form_websiteDomain}", + allowedHeaders: ['Content-Type', ...supertokens.getAllCORSHeaders()], + credentials: true, +}); +// highlight-end + +(async () => { + // highlight-next-line + await fastify.register(formDataPlugin); + // highlight-next-line + await fastify.register(plugin); + + await fastify.listen(8000); +})(); + +// ...your API routes +``` + + + + +:::important +Add the `middleware` BEFORE all your routes. +::: + +```tsx +import Koa from "koa"; +import cors from '@koa/cors'; +import supertokens from "supertokens-node"; +import { middleware } from "supertokens-node/framework/koa"; + +let app = new Koa(); + +app.use(cors({ + // highlight-start + origin: "^{form_websiteDomain}", + allowHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], + credentials: true, + // highlight-end +})); + +// highlight-next-line +app.use(middleware()); + +// ...your API routes +``` + + + + +:::important +Add the `middleware` BEFORE all your routes. +::: + +```tsx +import { RestApplication } from "@loopback/rest"; +import supertokens from "supertokens-node"; +import { middleware } from "supertokens-node/framework/loopback"; + +let app = new RestApplication({ + rest: { + cors: { + // highlight-start + origin: "^{form_websiteDomain}", + allowedHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], + credentials: true + // highlight-end + } + } +}); + +// highlight-next-line +app.middleware(middleware); + +// ...your API routes +``` + + + + + + + + +Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. + +```go +import ( + "net/http" + "strings" + + "github.com/supertokens/supertokens-golang/supertokens" +) + +func main() { + // SuperTokens init... + + //highlight-start + http.ListenAndServe("SERVER ADDRESS", corsMiddleware( + supertokens.Middleware(http.HandlerFunc(func(rw http.ResponseWriter, + //highlight-end + r *http.Request) { + // TODO: Handle your APIs.. + + })))) +} + +func corsMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(response http.ResponseWriter, r *http.Request) { + response.Header().Set("Access-Control-Allow-Origin", "^{form_websiteDomain}") + response.Header().Set("Access-Control-Allow-Credentials", "true") + if r.Method == "OPTIONS" { + // we add content-type + other headers used by SuperTokens + response.Header().Set("Access-Control-Allow-Headers", + strings.Join(append([]string{"Content-Type"}, + //highlight-start + supertokens.GetAllCORSHeaders()...), ",")) + //highlight-end + response.Header().Set("Access-Control-Allow-Methods", "*") + response.Write([]byte("")) + } else { + next.ServeHTTP(response, r) + } + }) +} +``` + + + + +Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. + +```go +import ( + "net/http" + + "github.com/gin-contrib/cors" + "github.com/gin-gonic/gin" + "github.com/supertokens/supertokens-golang/supertokens" +) + +func main() { + // SuperTokens init... + + router := gin.New() + + // CORS + router.Use(cors.New(cors.Config{ + AllowOrigins: []string{"^{form_websiteDomain}"}, + AllowMethods: []string{"GET", "POST", "DELETE", "PUT", "OPTIONS"}, + AllowHeaders: append([]string{"content-type"}, + // highlight-next-line + supertokens.GetAllCORSHeaders()...), + AllowCredentials: true, + })) + + // Adding the SuperTokens middleware + // highlight-start + router.Use(func(c *gin.Context) { + supertokens.Middleware(http.HandlerFunc( + func(rw http.ResponseWriter, r *http.Request) { + c.Next() + })).ServeHTTP(c.Writer, c.Request) + // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly + c.Abort() + }) + // highlight-end + + // Add APIs and start server +} +``` + + + + +Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. + +```go +import ( + "github.com/go-chi/chi" + "github.com/go-chi/cors" + "github.com/supertokens/supertokens-golang/supertokens" +) + +func main() { + // SuperTokens init... + + r := chi.NewRouter() + + // CORS + r.Use(cors.Handler(cors.Options{ + AllowedOrigins: []string{"^{form_websiteDomain}"}, + AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, + AllowedHeaders: append([]string{"Content-Type"}, + //highlight-next-line + supertokens.GetAllCORSHeaders()...), + AllowCredentials: true, + })) + + // SuperTokens Middleware + //highlight-next-line + r.Use(supertokens.Middleware) + + // Add APIs and start server +} +``` + + + +Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. + +```go +import ( + "net/http" + + "github.com/gorilla/handlers" + "github.com/gorilla/mux" + "github.com/supertokens/supertokens-golang/supertokens" +) + +func main() { + // SuperTokens init... + + // TODO: Add APIs + + router := mux.NewRouter() + + // Adding handlers.CORS(options)(supertokens.Middleware(router))) + //highlight-start + http.ListenAndServe("SERVER ADDRESS", handlers.CORS( + handlers.AllowedHeaders(append([]string{"Content-Type"}, + supertokens.GetAllCORSHeaders()...)), + handlers.AllowedMethods([]string{"GET", "POST", "PUT", "HEAD", "OPTIONS"}), + handlers.AllowedOrigins([]string{"^{form_websiteDomain}"}), + handlers.AllowCredentials(), + )(supertokens.Middleware(router))) + //highlight-end +} +``` + + + + + + + + + +Use the `Middleware` (**BEFORE all your routes**) and the `get_all_cors_headers()` functions as shown below. + +```python +from supertokens_python import get_all_cors_headers +from fastapi import FastAPI +from starlette.middleware.cors import CORSMiddleware +from supertokens_python.framework.fastapi import get_middleware + +app = FastAPI() +# highlight-next-line +app.add_middleware(get_middleware()) + +# TODO: Add APIs + +app.add_middleware( + CORSMiddleware, + allow_origins=[ + "^{form_websiteDomain}" + ], + allow_credentials=True, + allow_methods=["GET", "PUT", "POST", "DELETE", "OPTIONS", "PATCH"], + # highlight-next-line + allow_headers=["Content-Type"] + get_all_cors_headers(), +) + +# TODO: start server +``` + + + + +- Use the `Middleware` (**BEFORE all your routes and after calling init function**) and the `get_all_cors_headers()` functions as shown below. +- Add a route to catch all paths and return a 404. This is needed because if we don't add this, then OPTIONS request for the APIs exposed by the `Middleware` will return a `404`. + +```python +from supertokens_python import get_all_cors_headers +from flask import Flask, abort +from flask_cors import CORS # type: ignore +from supertokens_python.framework.flask import Middleware + +app = Flask(__name__) +# highlight-next-line +Middleware(app) + +# TODO: Add APIs + +CORS( + app=app, + origins=[ + "^{form_websiteDomain}" + ], + supports_credentials=True, + # highlight-next-line + allow_headers=["Content-Type"] + get_all_cors_headers(), +) + +# This is required since if this is not there, then OPTIONS requests for +# the APIs exposed by the supertokens' Middleware will return a 404 +# highlight-start +@app.route('/', defaults={'u_path': ''}) # type: ignore +@app.route('/') # type: ignore +def catch_all(u_path: str): + abort(404) +# highlight-end + +# TODO: start server +``` + + + + +Use the `Middleware` and the `get_all_cors_headers()` functions as shown below in your `settings.py`. + +```python +from supertokens_python import get_all_cors_headers +from typing import List +from corsheaders.defaults import default_headers + +CORS_ORIGIN_WHITELIST = [ + "^{form_websiteDomain}" +] + +CORS_ALLOW_CREDENTIALS = True + +CORS_ALLOWED_ORIGINS = [ + "^{form_websiteDomain}" +] + +CORS_ALLOW_HEADERS: List[str] = list(default_headers) + [ + "Content-Type" + # highlight-next-line +] + get_all_cors_headers() + +INSTALLED_APPS = [ + 'corsheaders', + 'supertokens_python' +] + +MIDDLEWARE = [ + 'corsheaders.middleware.CorsMiddleware', + ..., + # highlight-next-line + 'supertokens_python.framework.django.django_middleware.middleware', +] +# TODO: start server +``` + + + + + + + + + + + +You can review all the endpoints that are added through the use of **SuperTokens** by visiting the [API Specs](https://app.swaggerhub.com/apis/supertokens/FDI). + + + + + +## 5. Add the SuperTokens Error Handler + + + + + + + +Depending on the language and framework that you are using, you might need to add a custom error handler to your server. +The handler will catch all the authentication related errors and return proper HTTP responses that can be parsed by the frontend SDKs. + + + + + + +```tsx +import express, { Request, Response, NextFunction } from 'express'; +import { errorHandler } from "supertokens-node/framework/express"; + +let app = express(); + +// ...your API routes + +// highlight-start +// Add this AFTER all your routes +app.use(errorHandler()) +// highlight-end + +// your own error handler +app.use((err: unknown, req: Request, res: Response, next: NextFunction) => { /* ... */ }); +``` + + + +No additional `errorHandler` is required. + + + + +Add the `errorHandler` **Before all your routes and plugin registration** + +```tsx +import Fastify from "fastify"; +import { errorHandler } from "supertokens-node/framework/fastify"; + +let fastify = Fastify(); + +// highlight-next-line +fastify.setErrorHandler(errorHandler()); + +// ...your API routes +``` + + + +No additional `errorHandler` is required. + + + + +No additional `errorHandler` is required. + + + + + + +:::info +You can skip this step +::: + + + + +:::info +You can skip this step +::: + + + + + + +## 6. Secure Application Routes + +Now that your server can authenticate users, the final step that you need to take care of is to prevent unauthorized access to certain parts of the application. + + + + +For your APIs that require a user to be logged in, use the `verifySession` middleware. + + + + + +```tsx +import express from "express"; +import { verifySession } from "supertokens-node/recipe/session/framework/express"; +import { SessionRequest } from "supertokens-node/framework/express"; + +let app = express(); + +// highlight-start +app.post("/like-comment", verifySession(), (req: SessionRequest, res) => { + let userId = req.session!.getUserId(); + // highlight-end + //.... +}); +``` + + + + +```tsx +import Hapi from "@hapi/hapi"; +import { verifySession } from "supertokens-node/recipe/session/framework/hapi"; +import { SessionRequest } from "supertokens-node/framework/hapi"; + +let server = Hapi.server({ port: 8000 }); + +server.route({ + path: "/like-comment", + method: "post", + //highlight-start + options: { + pre: [ + { + method: verifySession() + }, + ], + }, + handler: async (req: SessionRequest, res) => { + let userId = req.session!.getUserId(); + //highlight-end + //... + } +}) +``` + + + +```tsx +import Fastify from "fastify"; +import { verifySession } from "supertokens-node/recipe/session/framework/fastify"; +import { SessionRequest } from "supertokens-node/framework/fastify"; + +let fastify = Fastify(); + +//highlight-start +fastify.post("/like-comment", { + preHandler: verifySession(), +}, (req: SessionRequest, res) => { + let userId = req.session!.getUserId(); + //highlight-end + //.... +}); +``` + + + + +```tsx +import KoaRouter from "koa-router"; +import { verifySession } from "supertokens-node/recipe/session/framework/koa"; +import { SessionContext } from "supertokens-node/framework/koa"; + +let router = new KoaRouter(); + +//highlight-start +router.post("/like-comment", verifySession(), (ctx: SessionContext, next) => { + let userId = ctx.session!.getUserId(); + //highlight-end + //.... +}); +``` + + + + +```tsx +import { inject, intercept } from "@loopback/core"; +import { RestBindings, MiddlewareContext, post, response } from "@loopback/rest"; +import { verifySession } from "supertokens-node/recipe/session/framework/loopback"; +import { SessionContext } from "supertokens-node/framework/loopback"; + +class LikeComment { + //highlight-start + constructor(@inject(RestBindings.Http.CONTEXT) private ctx: MiddlewareContext) { } + @post("/like-comment") + @intercept(verifySession()) + @response(200) + handler() { + let userId = (this.ctx as SessionContext).session!.getUserId(); + //highlight-end + //.... + } +} +``` + + + + + + +For your APIs that require a user to be logged in, use the `VerifySession` middleware. + + + + +```go +import ( + "fmt" + "net/http" + + "github.com/supertokens/supertokens-golang/recipe/session" +) + +func main() { + _ = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + // Wrap the API handler in session.VerifySession + session.VerifySession(nil, likeCommentAPI).ServeHTTP(rw, r) + }) +} + +func likeCommentAPI(w http.ResponseWriter, r *http.Request) { + // retrieve the session object as shown below + sessionContainer := session.GetSessionFromRequestContext(r.Context()) + + userID := sessionContainer.GetUserID() + + fmt.Println(userID) +} +``` + + + + +```go +import ( + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/supertokens/supertokens-golang/recipe/session" + "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" +) + +func main() { + router := gin.New() + + // Wrap the API handler in session.VerifySession + router.POST("/likecomment", verifySession(nil), likeCommentAPI) +} + +// This is a function that wraps the supertokens verification function +// to work the gin +func verifySession(options *sessmodels.VerifySessionOptions) gin.HandlerFunc { + return func(c *gin.Context) { + session.VerifySession(options, func(rw http.ResponseWriter, r *http.Request) { + c.Request = c.Request.WithContext(r.Context()) + c.Next() + })(c.Writer, c.Request) + // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly + c.Abort() + } +} + +func likeCommentAPI(c *gin.Context) { + // retrieve the session object as shown below + sessionContainer := session.GetSessionFromRequestContext(c.Request.Context()) + + userID := sessionContainer.GetUserID() + + fmt.Println(userID) +} +``` + + + + +```go +import ( + "fmt" + "net/http" + + "github.com/go-chi/chi" + "github.com/supertokens/supertokens-golang/recipe/session" +) + +func main() { + r := chi.NewRouter() + + // Wrap the API handler in session.VerifySession + r.Post("/likecomment", session.VerifySession(nil, likeCommentAPI)) +} + +func likeCommentAPI(w http.ResponseWriter, r *http.Request) { + // retrieve the session object as shown below + sessionContainer := session.GetSessionFromRequestContext(r.Context()) + + userID := sessionContainer.GetUserID() + + fmt.Println(userID) +} +``` + + + + +```go +import ( + "fmt" + "net/http" + + "github.com/gorilla/mux" + "github.com/supertokens/supertokens-golang/recipe/session" +) + +func main() { + router := mux.NewRouter() + + // Wrap the API handler in session.VerifySession + router.HandleFunc("/likecomment", session.VerifySession(nil, likeCommentAPI)).Methods(http.MethodPost) +} + +func likeCommentAPI(w http.ResponseWriter, r *http.Request) { + // retrieve the session object as shown below + sessionContainer := session.GetSessionFromRequestContext(r.Context()) + + userID := sessionContainer.GetUserID() + + fmt.Println(userID) +} +``` + + + + + + +For your APIs that require a user to be logged in, use the `verify_session` middleware. + + + + +```python +from supertokens_python.recipe.session.framework.fastapi import verify_session +from supertokens_python.recipe.session import SessionContainer +from fastapi import Depends + +# highlight-start +@app.post('/like_comment') # type: ignore +async def like_comment(session: SessionContainer = Depends(verify_session())): + user_id = session.get_user_id() + # highlight-end + + print(user_id) +``` + + + + +```python +from supertokens_python.recipe.session.framework.flask import verify_session +from supertokens_python.recipe.session import SessionContainer +from flask import g + +# highlight-start +@app.route('/update-jwt', methods=['POST']) # type: ignore +@verify_session() +def like_comment(): + session: SessionContainer = g.supertokens # type: ignore + + user_id = session.get_user_id() + # highlight-end + + print(user_id) +``` + + + + +```python +from supertokens_python.recipe.session.framework.django.asyncio import verify_session +from django.http import HttpRequest +from supertokens_python.recipe.session import SessionContainer + +# highlight-start +@verify_session() +async def like_comment(request: HttpRequest): + session: SessionContainer = request.supertokens # type: ignore + + user_id = session.get_user_id() + # highlight-end + + print(user_id) +``` + + + + + + + +The middleware function returns a `401` to the frontend if a session doesn't exist, or if the access token has expired, in which case, our frontend SDK automatically refreshes the session. + +In case of successful session verification, you get access to a `session` object using which you can get the user's ID, or manipulate the session information. + +## 7. Test the Login Flow + +Now that you have configured both the frontend and the backend, you can return to the frontend login page. +From here follow these steps to confirm that your setup is working properly. +- Click on the **Sign up** button to create a new account. +- After you have created the account go to **Login** page and fill in your credentials. +- If you are greeted with the login screen you have completed the quickstart setup. + +:::success 🎉 Congratulations 🎉 + +You've successfully integrated **SuperTokens** with your existing application! + +Of course, there are additional things that you should add in order to provide a complete authentication experience. +We will talk about those things in the [next section](./next-steps). + +::: diff --git a/v2/thirdpartyemailpassword/quickstart/frontend-setup.mdx b/v2/thirdpartyemailpassword/quickstart/frontend-setup.mdx new file mode 100644 index 000000000..734b91339 --- /dev/null +++ b/v2/thirdpartyemailpassword/quickstart/frontend-setup.mdx @@ -0,0 +1,925 @@ +--- +id: frontend-setup +title: Frontend Setup +hide_title: true +show_ui_switcher: true +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" +import WebJsInjector from "/src/components/webJsInjector" + +import FrontendCustomUISDKInstall from '../../community/reusableMD/custom-ui/frontent-custom-ui-sdk-install.mdx' +import FrontendCustomUISessionTokens from '../../community/reusableMD/custom-ui/frontend-custom-ui-session-tokens.mdx' +import FrontendCustomUISessionManagement from '../../community/reusableMD/custom-ui/frontend-custom-ui-session-management.mdx' +import FrontendCustomUISignout from '../../community/reusableMD/custom-ui/frontend-custom-ui-signout.mdx' +import FrontendCustomUIThirdParty from '../../community/reusableMD/custom-ui/frontend-custom-ui-thirdparty.mdx' +import FrontendCustomUIEmailPasswordSingUp from '../../community/reusableMD/custom-ui/frontend-custom-ui-email-password-sign-up.mdx' +import FrontendCustomUIEmailPasswordSingIn from '../../community/reusableMD/custom-ui/frontend-custom-ui-email-password-sign-in.mdx' +import FrontendSDKInstall from "../../community/reusableMD/frontend-sdk-install.mdx" + + +import {CustomUILink, PrebuiltUILink, PreBuiltOrCustomUISwitcher, PreBuiltUIContent, CustomUIContent} from "/src/components/preBuiltOrCustomUISwitcher" +import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" +import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" +import PreBuiltUIInjector from "/src/components/prebuiltuiInjector" +import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; +import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" +import {Question, Answer}from "/src/components/question" +import RRDVersionSubTabs from "/src/components/tabs/RRDVersionSubTabs" +import AppInfoForm from "/src/components/appInfoForm" +import NodeJSFrameworkSubTabsServerless from "/src/components/tabs/NodeJSFrameworkSubTabsServerless" +import NpmVersionOrYarnSubTabs from "/src/components/tabs/NpmVersionOrYarnSubTabs" +import CoreInjector from "/src/components/coreInjector" +import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs" +import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" +import BackendSDKCasing from "/src/components/BackendSDKCasing" + +# Frontend Setup + +Start the setup by configuring your frontend application to use **SuperTokens** for authentication. + + + + + +This guide uses the **SuperTokens Pre Built UI** components. +If you want to create your own interface please check the **Custom UI** tutorial. + +## 1. Install the SDK + + + + + +Run the following command in your terminal to install the package. + + + + + +## 2. Initialize the SDK + + + + + + + + + + + +In your main application file call the `SuperTokens.init` function to initialize the SDK. +The `init` call includes the [main configuration details](../appinfo), as well as the **recipes** that you will be using in your setup. +After that you will have to wrap the application with the `SuperTokensWrapper` component. +This will provide authentication context for the rest of the UI tree. + + + +```tsx +import React from 'react'; + +import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; +import ThirdParty, { Github, Google, Facebook, Apple } from "supertokens-auth-react/recipe/thirdparty"; +import EmailPassword from "supertokens-auth-react/recipe/emailpassword"; +import Session from "supertokens-auth-react/recipe/session"; + +// highlight-start +SuperTokens.init({ + appInfo: { + // learn more about this on https://supertokens.com/docs/thirdpartyemailpassword/appinfo + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + websiteDomain: "^{form_websiteDomain}", + apiBasePath: "^{form_apiBasePath}", + websiteBasePath: "^{form_websiteBasePath}" + }, + recipeList: [ + ThirdParty.init({ + signInAndUpFeature: { + providers: [ + Github.init(), + Google.init(), + Facebook.init(), + Apple.init(), + ] + } + }), + EmailPassword.init(), + Session.init() + ] +}); +// highlight-end + + +/* Your App */ +class App extends React.Component { + render() { + return ( + // highlight-next-line + + {/*Your app components*/} + // highlight-next-line + + ); + } +} +``` + + + + + + + + + + + +Before we initialize the `supertokens-web-js` SDK let's see how we will use it in our Angular app + +**Architecture** + +- The `supertokens-web-js` SDK is responsible for session management and providing helper functions to check if a session exists, or validate the access token claims on the frontend (for example, to check for user roles before showing some UI). We will initialise this SDK on the root of your Angular app, so that all pages in your app can use it. +- We will create a `^{form_websiteBasePath}*` route in the Angular app which will render our pre built UI which will also need to be initialised, but only on that route. + +**Creating the `^{form_websiteBasePath}` route** + +- Use the Angular CLI to generate a new route + + ```bash + ng generate module auth --route auth --module app.module + ``` + +- Add the following code to your `auth` angular component + + ```tsx title="/app/auth/auth.component.ts" + import {init as supertokensUIInit} from "supertokens-auth-react-script"; + import supertokensUIEmailPassword from "supertokens-auth-react-script/recipe/emailpassword"; + import supertokensUIThirdParty from "supertokens-auth-react-script/recipe/thirdparty"; + import supertokensUISession from "supertokens-auth-react-script/recipe/session"; + 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: [ + supertokensUIEmailPassword.init(), + supertokensUIThirdParty.init({ + signInAndUpFeature: { + providers: [ + supertokensUIThirdParty.Github.init(), + supertokensUIThirdParty.Google.init(), + supertokensUIThirdParty.Facebook.init(), + supertokensUIThirdParty.Apple.init(), + ] + } + }), + supertokensUISession.init(), + ], + }); + } + this.renderer.appendChild(this.document.body, script); + } + } + ``` + - In the `loadScript` function, we provide the SuperTokens config for the UI. We add the emailpassword, session and social login recipes along with Github, Google, Facebook and Apple login buttons. + +- Initialize the `supertokens-web-js` SDK in your angular app's root component. This will provide session management across your entire application. + + ```tsx title="/app/app.component.ts " + import SuperTokens from "supertokens-web-js"; + import Session from "supertokens-web-js/recipe/session"; + + SuperTokens.init({ + appInfo: { + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}", + }, + recipeList: [ + Session.init(), + ], + }); + ``` + +
      + +
      + +
      + + + + + + + +Before we initialize the `supertokens-web-js` SDK let's see how we will use it in our Vue app + +**Architecture** + +- The `supertokens-web-js` SDK is responsible for session management and providing helper functions to check if a session exists, or validate the access token claims on the frontend (for example, to check for user roles before showing some UI). We will initialise this SDK on the root of your Vue app, so that all pages in your app can use it. +- We will create a `^{form_websiteBasePath}*` route in the Vue app which will render our pre built UI which will also need to be initialised, but only on that route. + +**Creating the `^{form_websiteBasePath}` route** + +- Create a new file `AuthView.vue`, this Vue component will be used to render the auth component: + ```tsx + import {init as supertokensUIInit} from "supertokens-auth-react-script"; + import supertokensUIThirdParty from "supertokens-auth-react-script/recipe/thirdparty"; + import supertokensUIEmailPassword from "supertokens-auth-react-script/recipe/emailpassword"; + import supertokensUISession from "supertokens-auth-react-script/recipe/session"; + + + + ``` + - In the `loadScript` function, we provide the SuperTokens config for the UI. We add the emailpassword, session and social login recipes along with Github, Google, Facebook and Apple login buttons. + +- Initialize the `supertokens-web-js` SDK in your Vue app's `main.ts` file. This will provide session management across your entire application. + + ```tsx title="/main.ts " + // @ts-ignore + import { createApp } from "vue"; + import SuperTokens from "supertokens-web-js"; + import Session from "supertokens-web-js/recipe/session"; + // @ts-ignore + import App from "./App.vue"; + // @ts-ignore + import router from "./router"; + + SuperTokens.init({ + appInfo: { + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}", + }, + recipeList: [ + Session.init(), + ], + }); + + const app = createApp(App); + + app.use(router); + + app.mount("#app"); + + ``` + + + + + + + + + +:::important +SuperTokens does not support pre-built UI for mobile frameworks. Please toggle the **Custom UI** option from the *Table of Contents* section. +::: + + + +
      + +## 3. Configure Routing + + + + + + + + +In order for the **Pre Built UI** to be rendered inside your application, will will have to specify which routes will show the authentication components. +The **React SDK** uses [**React Router**](https://reactrouter.com/en/main) under the hood to achieve this. +Based on whether you already use this package or not in your project, there are two different ways of configuring the routes. + + + + + + + + + +Call the `getSuperTokensRoutesForReactRouterDom` method from within any `react-router-dom` `Routes` component. + +```tsx +import React from 'react'; +import { + BrowserRouter, + Routes, + Route, + Link +} from "react-router-dom"; + +// highlight-next-line +import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; +import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui"; +^{prebuiltuiimport} +import * as reactRouterDom from "react-router-dom"; + +class App extends React.Component { + render() { + return ( + + + + {/*This renders the login UI on the ^{form_websiteBasePath} route*/} + // highlight-next-line + {getSuperTokensRoutesForReactRouterDom(reactRouterDom, [^{recipePreBuiltUINameCapitalLetters}])} + {/*Your app routes*/} + + + + ); + } +} +``` + + + + + +Call the `getSuperTokensRoutesForReactRouterDom` method from within any `react-router-dom` `Switch` component. + +```tsx +import React from 'react'; +import { + BrowserRouter, + Switch, + Route, + Link +} from "react-router-dom5"; + +// highlight-next-line +import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; +import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui"; +^{prebuiltuiimport} +import * as reactRouterDom from "react-router-dom"; + + +class App extends React.Component { + render() { + return ( + + + + {/*This renders the login UI on the ^{form_websiteBasePath} route*/} + // highlight-next-line + {getSuperTokensRoutesForReactRouterDom(reactRouterDom, [^{recipePreBuiltUINameCapitalLetters}])} + {/*Your app routes*/} + + + + ); + } +} +``` + + + + + + + + +Add the highlighted code snippet to your root level `render` function. + +```tsx +import React from 'react'; +^{prebuiltuiimport} +import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; +import { canHandleRoute, getRoutingComponent } from "supertokens-auth-react/ui"; + +class App extends React.Component { + render() { + // highlight-start + if (canHandleRoute([^{recipePreBuiltUINameCapitalLetters}])) { + // This renders the login UI on the ^{form_websiteBasePath} route + return getRoutingComponent([^{recipePreBuiltUINameCapitalLetters}]) + } + // highlight-end + + return ( + {/*Your app*/} + ); + } + +} +``` + + + + + + + + + + +Update your angular router so that all auth related requests load the `auth` component + +```tsx title="/app/app-routing.module.ts" +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +const routes: Routes = [ + // highlight-start + { + path: "^{form_websiteBasePath_withoutForwardSlash}", + // @ts-ignore + loadChildren: () => import("./auth/auth.module").then((m) => m.AuthModule), + }, + + // @ts-ignore + { path: "**", loadChildren: () => import("./home/home.module").then((m) => m.HomeModule) }, + // highlight-end +]; + +@NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule], +}) +export class AppRoutingModule {} +``` + + + + + + + + +Update your Vue router so that all auth related requests load the `AuthView` component + +```tsx title="/router/index.ts" +// @ts-ignore +import { createRouter, createWebHistory } from "vue-router"; +// @ts-ignore +import HomeView from "../views/HomeView.vue"; +// @ts-ignore +import AuthView from "../views/AuthView.vue"; + +const router = createRouter({ + // @ts-ignore + history: createWebHistory(import.meta.env.BASE_URL), + routes: [ + { + path: "/", + name: "home", + component: HomeView, + }, + { + path: "^{form_websiteBasePath}/:pathMatch(.*)*", + name: "auth", + component: AuthView, + }, + ], +}); + +export default router; +``` + + + + + + + +:::important +SuperTokens does not support pre-built UI for mobile frameworks. Please toggle the **Custom UI** option from the *Table of Contents* section. +::: + + + + + + + +## 4. Handle Session Tokens + + + + + +This part is handled automatically by the **Frontend SDK**. +You don not need to do anything. +The step serves more as a way for us to tell you how is this handled under the hood. + +After you call the `init` function, the **SDK** will add interceptors to both `fetch` and `XHR`, XMLHTTPRequest. The latter is used by the `axios` library. +The interceptors save the session tokens that are generated from the authentication flow. +Those tokens are then added to requests initialized by your frontend app which target the backend API. +By default, the tokens are stored through session cookies but you can also switch to [header based authentication](../common-customizations/sessions/token-transfer-method). + + + +## 5. Secure Application Routes + +In order to prevent unauthorized access to ceratain parts of your frontend application you can use our utilities. +Follow the code samples below to understand how to do this. + + + + + +You can wrap your components with the `` react component. This will ensure that your component renders only if the user is logged in. If they are not logged in, the user will be redirected to the login page. + +```tsx +import React from "react"; +import { + BrowserRouter, + Routes, + Route, +} from "react-router-dom"; +// highlight-next-line +import { SessionAuth } from "supertokens-auth-react/recipe/session"; +// @ts-ignore +import MyDashboardComponent from "./dashboard"; + +class App extends React.Component { + render() { + return ( + + + + {/*Components that require to be protected by authentication*/} + + + // highlight-end + } /> + + + ); + } +} +``` + + + + + +You can use the `doesSessionExist` function to check if a session exists in all your routes. + +```tsx +import Session from 'supertokens-web-js/recipe/session'; + +async function doesSessionExist() { + if (await Session.doesSessionExist()) { + // user is logged in + } else { + // user has not logged in yet + } +} +``` + + + + + +## 6. View the login UI + + + +You can check the login UI by visiting the `^{form_websiteBasePath}` route, in your frontend application. +To review all the components of our pre-built UI please follow [this link](https://master--6571be2867f75556541fde98.chromatic.com/?path=/story/auth-page--playground). + + + + + +
      + + + +This guide shows you how to create your own UI on top of the **SuperTokens SDK**. +If you want to use our **Pre Built Components** please check the following tutorial. + +## 1. Install the SDK + + + +## 2. Initialize the SDK + +Call the SDK init function at the start of your application. +The invocation includes the [main configuration details](../appinfo), as well as the **recipes** that you will be using in your setup. + + + + + + + + + +```tsx +import SuperTokens from 'supertokens-web-js'; +import Session from 'supertokens-web-js/recipe/session'; +import ^{recipeNameCapitalLetters} from 'supertokens-web-js/recipe/^{codeImportRecipeName}' + +SuperTokens.init({ + appInfo: { + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}", + appName: "...", + }, + recipeList: [ + Session.init(), + ^{recipeNameCapitalLetters}.init(), + ], +}); +``` + + + + + + + + + +```tsx +import supertokens from "supertokens-web-js-script"; +import supertokensSession from "supertokens-web-js-script/recipe/session"; +import supertokens^{recipeNameCapitalLetters} from 'supertokens-web-js-script/recipe/^{codeImportRecipeName}' +supertokens.init({ + appInfo: { + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}", + appName: "...", + }, + recipeList: [ + supertokensSession.init(), + supertokens^{recipeNameCapitalLetters}.init(), + ], +}); +``` + + + + + + + + + + + + + + + + + +```tsx +import SuperTokens from 'supertokens-react-native'; + +SuperTokens.init({ + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}", +}); +``` + + + + + + + + + +Add the `SuperTokens.init` function call at the start of your application. + +```kotlin +import android.app.Application +import com.supertokens.session.SuperTokens + +class MainApplication: Application() { + override fun onCreate() { + super.onCreate() + + SuperTokens.Builder(this, "^{form_apiDomain}") + .apiBasePath("^{form_apiBasePath}") + .build() + } +} +``` + + + + + + + + + + +```swift +import UIKit +import SuperTokensIOS + +fileprivate class ApplicationDelegate: UIResponder, UIApplicationDelegate { + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + do { + try SuperTokens.initialize( + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}" + ) + } catch SuperTokensError.initError(let message) { + // TODO: Handle initialization error + } catch { + // Some other error + } + + return true + } + +} +``` + + + + + + + + + +```dart +import 'package:supertokens_flutter/supertokens.dart'; + +void main() { + SuperTokens.init( + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}", + ); +} +``` + + + + + + + + + + + + +## 3. Add the Login UI for the Email/Password Flow + +The **Email/Password** flow involves two types of user interfaces. +One for registering and creating new users, the *Sign Up Form*. +And one for the actual authentication attempt, the *Sign In Form*. +If you are provisioning users from a different method you can skip over adding the sign up form. + +### 3.1 Add the Sign Up form + + + +### 3.2 Add the Sign In Form + + + +## 4. Add the Login UI for the ThirdParty Flow + + + +## 5. Handle Session Tokens + + + +## 6. Protect Frontend Routes + + + +## 7. Add a Sign Out Action + + + + + + + +
      + + +:::success 🎉 Congratulations 🎉 + +Congratulations! You've successfully integrated your frontend app with SuperTokens. + +The [next section](./backend-setup) will guide you through setting up your backend and then you should be able to complete a login flow. + +::: + diff --git a/v2/thirdpartyemailpassword/quickstart/introduction.mdx b/v2/thirdpartyemailpassword/quickstart/introduction.mdx new file mode 100644 index 000000000..ea50c2223 --- /dev/null +++ b/v2/thirdpartyemailpassword/quickstart/introduction.mdx @@ -0,0 +1,85 @@ +--- +id: introduction +title: Introduction +hide_title: true +show_ui_switcher: false +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + +# Introduction + +## Overview + +This quickstart will guide you through how to set up a basic project that uses **SuperTokens** to authenticate users. +The tutorial shows both an **Email/Password** and a **ThirdParty** login flow, rendered by either our **Prebuilt UI components** or by your own **Custom UI**. + + + + + +If you want to skip straight to an example application you can choose between: +- Checking our live [demo application](https://^{docsLinkRecipeName}.demo.supertokens.com/auth) +- Running a **SuperTokens** project from your local machine. You just have to use our CLI app and execute the following command: + +```bash +npx create-supertokens-app@latest --recipe=^{docsLinkRecipeName} +``` + + + + +## Before you start + + + + + +Before going into the actual tutorial, let's get a clear picture of how **SuperTokens** works and some of the terms that we will use throughout the documentation. + +### SuperTokens Core + +The main service that provides all the functionality is called the **SuperTokens Core**. SDKs communicate over an API with this service in order to +perform authentication related tasks. + +Unlike with other providers, the **SuperTokens Frontend SDK** never talks to the **Authentication Service** directly. +All the requests target your existing **Backend Service**. +From there, the **Backend SDKs** are used to expose new authentication routes. Those in turn communicate with the **SuperTokens Core**. + +You can check the following diagram for a high level overview of how the services will interact within an authentication setup that involves **SuperTokens**. + + + + Flowchart of architecture when using SuperTokens managed service + + + Flowchart of architecture when self-hosting SuperTokens + + + +:::info Edge Cases +- You can also host the **SuperTokens Core** yourself. In that case your backend will communicate with a service that exists inside your infrastructure. +- If you are using a backend for which we do not have an SDK, you will have to spin up an additional auth service in a language for which we do have a backend SDK (NodeJS, Python or Golang). +::: + + +### Recipes + +The functionalities that **SuperTokens** provides are bundled into objects that can be reffered to as **Recipes**. +Everything from *authentication methods* to *session and user management* can be included under this concept. +In the following sections, we will see how recipes get initialised and configured and how you can customise them to fit your use case. + + +Now that we have cleared all this out, we can move forward with the actual tutorial. +Go to the next page to see how to configure your [Frontend Application](./frontend-setup). + + + diff --git a/v2/thirdpartyemailpassword/quickstart/next-steps.mdx b/v2/thirdpartyemailpassword/quickstart/next-steps.mdx new file mode 100644 index 000000000..ddf50b09c --- /dev/null +++ b/v2/thirdpartyemailpassword/quickstart/next-steps.mdx @@ -0,0 +1,178 @@ +--- +id: next-steps +title: Next Steps +hide_title: true +show_ui_switcher: false +show_next_button: false +--- + +import Card from "/src/components/card/Card" +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +import PreBuiltUIInjector from "/src/components/prebuiltuiInjector" +import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; +import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" +import {Question, Answer}from "/src/components/question" +import RRDVersionSubTabs from "/src/components/tabs/RRDVersionSubTabs" +import AppInfoForm from "/src/components/appInfoForm" +import NodeJSFrameworkSubTabsServerless from "/src/components/tabs/NodeJSFrameworkSubTabsServerless" +import CoreInjector from "/src/components/coreInjector" +import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs" +import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" +import BackendSDKCasing from "/src/components/BackendSDKCasing" + + + +# Next Steps + + + + + +## Overview + +Now that you have completed the quickstart guide there are a few more things that you need to take care of on the road towards a production ready authentication experience. + +## Configure the Core Service + +If you have signed up and deployed a SuperTokens environment already, you can skip this step. +Otherwise, please follow these instructions to use the correct **SuperTokens Core** instance in your application. + +The steps show you how to connect to a **SuperTokens Managed Service Environment**. +If you want to self host the core instance please check the [following guide](../pre-built-ui/setup/core/with-docker). + +### 1. Sign up for a SuperTokens account + +Open this [page](https://supertokens.com/auth) in order to access the account creation page. +Select the account that you want to use and wait for the action to complete. + +### 2. Select the authentication method + +After you have created your account, you will be prompted with a form that will ask you to specify details about your configuration. +Select which authentication method that you want to use. + +Integration with SuperTokens SDKs + +### 3. Select the region where you want to deploy the core service# + +SuperTokens environments can be deployed in 3 different regions: `US East (N. Virginia)`, `Europe (Ireland)`, `Asia Pacific (Singapore)`. +In order to avoid any latency issues please select a region that is closest to where your services are hosted. + +### 4. Click the deploy button 🚀 + +Integration with SuperTokens SDKs + +Our internal service will deploy a separate environment based on your selection. + +After this process is complete, you will be directed to the dashboard page. Here you can view and edit information about your newly created environment. + +The main thing that we want to focus is the **Connecting to a development instance** section. +There you can see two different values, the `connectionURI` and the `apiKey`. +You will use these values in the next step. + +:::info + +The initial setup flow only configures a development environment. In order to use SuperTokens in production, you will have to click the Create Production Env button. + +::: + +### 5. Connect the Backend SDK with SuperTokens 🔌 + +Add the `connectionURI` and the `apiKey` shown on the dashboard to your code on the backend. + +SuperTokens managed service dashboard connectionURI and API key + + + + +```tsx +import supertokens from "supertokens-node"; + +supertokens.init({ + // highlight-start + supertokens: { + connectionURI: "", + apiKey: "" + }, + // highlight-end + appInfo: { + apiDomain: "...", + appName: "...", + websiteDomain: "..." + }, + recipeList: [] +}); +``` + + + + +```go +import "github.com/supertokens/supertokens-golang/supertokens" + +func main() { + supertokens.Init(supertokens.TypeInput{ + // highlight-start + Supertokens: &supertokens.ConnectionInfo{ + ConnectionURI: "", + APIKey: "", + }, + // highlight-end + }) +} + +``` + + + + +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig + +init( + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + # highlight-start + supertokens_config=SupertokensConfig( + connection_uri='', + api_key='' + ), + # highlight-end + framework='...', # type: ignore + recipe_list=[ + #... + ] +) +``` + + + + + + + +## Customize Your Authentication Flow + +After you have connected the Backend SDK to a specific **SuperTokens Core Service**, you can go check the rest of the documentation. +There are several sections that show you how to customize the authentication experience to fit your specific needs. +Some of the most common subjects are: + +- [**Add Email Verification**](../common-customizations/email-verification/about) +- [**Add a Custom Redirect Action**](../pre-built-ui/auth-redirection) +- [**Use Custom Session Management**](../common-customizations/sessions/session-verification-in-api/get-session) +- [**Share Sessions Across Subdomains**](../common-customizations/sessions/share-sessions-across-sub-domains) +- [**Post Sign In Actions**](../common-customizations/handling-signinup-success) + + +## Explore Additional Features + +You can also review the additional features that **SuperTokens** exposes. +Those can help you extend the authentication implementation on different levels. + +- [**Self Host SuperTokens Core**](../pre-built-ui/setup/core/with-docker) +- [**Migration Guide**](../migration/about) +- [**Multi Factor Authentication**](../../mfa/introduction) +- [**Manage Users through the User Management Dashboard**](../pre-built-ui/setup/user-management-dashboard/setup) +- [**Multi Tenancy**](../../multitenancy/introduction) +- [**User Roles and Permissions**](../user-roles/initialisation) + diff --git a/v2/thirdpartyemailpassword/sidebars.js b/v2/thirdpartyemailpassword/sidebars.js index f5097140a..b4181d614 100644 --- a/v2/thirdpartyemailpassword/sidebars.js +++ b/v2/thirdpartyemailpassword/sidebars.js @@ -1,163 +1,35 @@ module.exports = { sidebar: [ { + label: "quickstart", type: "category", - label: "Start Here", customProps: { highlightGroup: true, }, collapsed: false, items: [ - "introduction", { - type: "category", - label: "Quick setup with Pre built UI", - customProps: { - categoryIcon: "lightning", - }, - items: [ - { - type: "category", - label: "Setup", - collapsed: false, - items: [ - "pre-built-ui/setup/frontend", - "pre-built-ui/setup/backend", - { - type: "category", - label: "Step 3: Core", - items: [ - { - type: "category", - label: "Self hosted", - items: [ - "pre-built-ui/setup/core/with-docker", - "pre-built-ui/setup/core/without-docker", - { - type: "category", - label: "Database Setup", - items: [ - "pre-built-ui/setup/database-setup/mysql", - "pre-built-ui/setup/database-setup/postgresql", - "pre-built-ui/setup/database-setup/rename-database-tables", - ], - }, - ], - }, - "pre-built-ui/setup/core/saas-setup", - ], - }, - { - type: "category", - label: "Step 4: User management dashboard", - items: [ - "pre-built-ui/setup/user-management-dashboard/setup", - "pre-built-ui/setup/user-management-dashboard/users-listing-and-details", - "pre-built-ui/setup/user-management-dashboard/managing-user-roles-and-permissions", - { - type: "category", - label: "Tenant Management", - collapsed: true, - items: [ - "pre-built-ui/setup/user-management-dashboard/tenant-management/overview", - "pre-built-ui/setup/user-management-dashboard/tenant-management/details", - "pre-built-ui/setup/user-management-dashboard/tenant-management/third-party", - ], - }, - ], - }, - ], - }, - "pre-built-ui/handling-session-tokens", - "pre-built-ui/securing-routes", - "pre-built-ui/sign-out", - "pre-built-ui/auth-redirection", - "pre-built-ui/enable-email-verification", - "pre-built-ui/multitenant-login", - { - type: "category", - label: "Further Reading", - items: [ - "pre-built-ui/further-reading/email-password-login", - "pre-built-ui/further-reading/thirdparty-login", - "pre-built-ui/further-reading/password-reset", - "pre-built-ui/further-reading/email-verification", - ], - }, - ], + id: "quickstart/introduction", + type: "doc", + label: "Introduction", }, { - type: "category", - label: "Using your own UI / Custom UI", - customProps: { - categoryIcon: "pencil", - }, - items: [ - { - type: "category", - label: "Initialisation", - collapsed: false, - items: [ - "custom-ui/init/frontend", - "custom-ui/init/backend", - { - type: "category", - label: "Step 3: Core", - items: [ - { - type: "category", - label: "Self hosted", - items: [ - "custom-ui/init/core/with-docker", - "custom-ui/init/core/without-docker", - { - type: "category", - label: "Database Setup", - items: [ - "custom-ui/init/database-setup/mysql", - "custom-ui/init/database-setup/postgresql", - "custom-ui/init/database-setup/rename-database-tables", - ], - }, - ], - }, - "custom-ui/init/core/saas-setup", - ], - }, - { - type: "category", - label: "Step 4: User management dashboard", - items: [ - "custom-ui/init/user-management-dashboard/setup", - "custom-ui/init/user-management-dashboard/users-listing-and-details", - "custom-ui/init/user-management-dashboard/managing-user-roles-and-permissions", - { - type: "category", - label: "Tenant Management", - collapsed: true, - items: [ - "custom-ui/init/user-management-dashboard/tenant-management/overview", - "custom-ui/init/user-management-dashboard/tenant-management/details", - "custom-ui/init/user-management-dashboard/tenant-management/third-party", - ], - }, - ], - }, - ], - }, - "custom-ui/email-password-login", - "custom-ui/thirdparty-login", - "custom-ui/handling-session-tokens", - "custom-ui/forgot-password", - "custom-ui/securing-routes", - "custom-ui/sign-out", - "custom-ui/enable-email-verification", - "custom-ui/multitenant-login", - ], + id: "quickstart/frontend-setup", + type: "doc", + label: "Frontend Setup", + }, + { + id: "quickstart/backend-setup", + type: "doc", + label: "Backend Setup", + }, + { + id: "quickstart/next-steps", + type: "doc", + label: "Next Steps", }, ], }, - "user-object", { type: "category", label: "Integrations", @@ -378,6 +250,11 @@ module.exports = { "common-customizations/sessions/protecting-frontend-routes", "common-customizations/sessions/with-jwt/read-jwt", "common-customizations/sessions/ssr", + { + id: "pre-built-ui/handling-session-tokens", + type: "doc", + label: "Access Session Tokens", + }, { type: "category", label: "Reading / modifying session claims", @@ -386,6 +263,12 @@ module.exports = { "common-customizations/sessions/claims/claim-validators", ], }, + { id: "pre-built-ui/sign-out", type: "doc", label: "Add Sign Out" }, + { + id: "pre-built-ui/auth-redirection", + type: "doc", + label: "Add Redirect Actions", + }, "common-customizations/sessions/revoke-session", "common-customizations/sessions/anonymous-session", "common-customizations/sessions/user-impersonation", @@ -499,6 +382,7 @@ module.exports = { "common-customizations/password-managers", ], }, + "add-multiple-clients-for-the-same-provider", "common-customizations/get-user-info", "common-customizations/user-pagination", "common-customizations/delete-user", @@ -600,6 +484,28 @@ module.exports = { }, "common-customizations/multiple-clients", "common-customizations/userid-format", + { + id: "pre-built-ui/setup/core/saas-setup", + label: "Connecting to the SuperTokens Core Managed Service", + type: "doc", + }, + { + type: "category", + label: "Self Hosting SuperTokens Core", + items: [ + "pre-built-ui/setup/core/with-docker", + "pre-built-ui/setup/core/without-docker", + { + type: "category", + label: "Database Setup", + items: [ + "pre-built-ui/setup/database-setup/mysql", + "pre-built-ui/setup/database-setup/postgresql", + "pre-built-ui/setup/database-setup/rename-database-tables", + ], + }, + ], + }, { type: "category", label: @@ -730,6 +636,24 @@ module.exports = { "mfa", "multi-tenant", "attack-protection-suite", + { + type: "category", + label: "User Management dashboard", + items: [ + "pre-built-ui/setup/user-management-dashboard/setup", + "pre-built-ui/setup/user-management-dashboard/users-listing-and-details", + "pre-built-ui/setup/user-management-dashboard/managing-user-roles-and-permissions", + { + type: "category", + label: "Tenant Management", + collapsed: true, + items: [ + "pre-built-ui/setup/user-management-dashboard/tenant-management/overview", + "pre-built-ui/setup/user-management-dashboard/tenant-management/details", + ], + }, + ], + }, ], }, "scalability", @@ -770,11 +694,22 @@ module.exports = { label: "References", items: [ "architecture", + "user-object", "other-frameworks", "appinfo", "sdks", "apis", "compatibility-table", + { + type: "category", + label: "Prebuilt UI Components", + items: [ + "pre-built-ui/further-reading/thirdparty-login", + "pre-built-ui/further-reading/email-password-login", + "pre-built-ui/further-reading/password-reset", + "pre-built-ui/further-reading/email-verification", + ], + }, ], }, ], diff --git a/v2/thirdpartypasswordless/add-multiple-clients-for-the-same-provider.mdx b/v2/thirdpartypasswordless/add-multiple-clients-for-the-same-provider.mdx new file mode 100644 index 000000000..960735447 --- /dev/null +++ b/v2/thirdpartypasswordless/add-multiple-clients-for-the-same-provider.mdx @@ -0,0 +1,209 @@ +--- +id: add-multiple-clients-for-the-same-provider +title: Add Multiple Clients for the Same Provider +hide_title: true +show_ui_switcher: false +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" +import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" +import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; +import AppInfoForm from "/src/components/appInfoForm" + +# Add Multiple Clients for the Same Provider + +If you have social/SSO login for your web and mobile app, then you might need to setup different Client ID/Secret for the same provider on the backend. +For example, in case of Apple login, Apple gives you different client IDs for iOS login vs web & Android login (same client ID for web and Android). + +In order to get this to work, you would need to add additional clients to the Apple.init on the backend. Each client would need to be uniquely identified and this is done using the `clientType` string. For example, you can add one `clientType` for `web-and-android` and one for `ios`. + + + + +```tsx +import { ProviderInput } from "supertokens-node/recipe/thirdparty/types"; + +let providers: ProviderInput[] = [ + { + config: { + thirdPartyId: "apple", + clients: [{ + clientType: "web-and-android", + clientId: "...", + additionalConfig: { + "keyId": "...", + "privateKey": "...", + "teamId": "...", + } + }, { + clientType: "ios", + clientId: "...", + additionalConfig: { + "keyId": "...", + "privateKey": "...", + "teamId": "...", + } + }] + } + } +] +``` + + + + +```go +import ( + "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" +) + +func main() { + _ = []tpmodels.ProviderInput{{ + Config: tpmodels.ProviderConfig{ + ThirdPartyId: "apple", + Clients: []tpmodels.ProviderClientConfig{ + { + ClientType: "web-and-android", + ClientID: "...", + AdditionalConfig: map[string]interface{}{ + "keyId": "...", + "privateKey": "...", + "teamId": "...", + }, + }, + { + ClientType: "ios", + ClientID: "...", + AdditionalConfig: map[string]interface{}{ + "keyId": "...", + "privateKey": "...", + "teamId": "...", + }, + }, + }, + }, + }} +} +``` + + + + +```python +from supertokens_python.recipe.thirdparty.provider import ProviderInput, ProviderConfig, ProviderClientConfig + +providers = [ + ProviderInput( + config=ProviderConfig( + third_party_id="apple", + clients=[ + ProviderClientConfig( + client_type="web-and-android", + client_id="...", + additional_config={ + "keyId": "...", + "privateKey": "...", + "teamId": "...", + }, + ), + ProviderClientConfig( + client_type="ios", + client_id="...", + additional_config={ + "keyId": "...", + "privateKey": "...", + "teamId": "...", + }, + ), + ], + ), + ), +] +``` + + + + +For the frontend, you would need to use the right `clientType` as shown below: + + + + + + + + + +We pass in the `clientType` during the init call. + +```tsx +import SuperTokens from 'supertokens-web-js'; + +SuperTokens.init({ + appInfo: { + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}", + appName: "...", + }, + // highlight-next-line + clientType: "web-and-android", + recipeList: [/*...*/], +}); +``` + +If you are using our pre built UI SDK (supertokens-auth-react) as well, you can provide the `clientType` config to it as follows: + +```tsx +import SuperTokens from 'supertokens-auth-react'; + +SuperTokens.init({ + appInfo: { + apiDomain: "^{form_apiDomain}", + websiteDomain: "^{form_websiteDomain}", + apiBasePath: "^{form_apiBasePath}", + appName: "...", + }, + // highlight-next-line + clientType: "web-and-android", + recipeList: [/*...*/], +}); +``` + + + + + + + + +We pass in the `clientType` during the init call. + +```tsx +import supertokens from "supertokens-web-js-script"; +supertokens.init({ + appInfo: { + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}", + appName: "...", + }, + // highlight-next-line + clientType: "web-and-android", + recipeList: [/*...*/], +}); +``` + + + + + + + + + + +When making calls to the APIs from your mobile app, the request body also takes a `clientType` prop as seen in the above API calls. + + + diff --git a/v2/thirdpartypasswordless/common-customizations/multi-tenancy/new-tenant-config.mdx b/v2/thirdpartypasswordless/common-customizations/multi-tenancy/new-tenant-config.mdx index fe4f3c4d5..464a16535 100644 --- a/v2/thirdpartypasswordless/common-customizations/multi-tenancy/new-tenant-config.mdx +++ b/v2/thirdpartypasswordless/common-customizations/multi-tenancy/new-tenant-config.mdx @@ -25,198 +25,6 @@ The configuration mapped to each tenant contains information about which login m - - - - - -```tsx -import Multitenancy from "supertokens-node/recipe/multitenancy"; -import { FactorIds } from "supertokens-node/recipe/multifactorauth"; - -async function createNewTenant() { - - let resp = await Multitenancy.createOrUpdateTenant("customer1", { - firstFactors: [FactorIds.OTP_PHONE, FactorIds.OTP_EMAIL, FactorIds.LINK_PHONE, FactorIds.LINK_EMAIL, FactorIds.THIRDPARTY] - }); - - if (resp.createdNew) { - // new tenant was created - } else { - // existing tenant's config was modified. - } -} -``` - -In the example above, we have added all factor IDs related to the passwordless recipe. However, you can choose to use a subset of them. The factor IDs are described below: -- ThirdParty: `FactorIds.THIRDPARTY` -- Passwordless: - - With email OTP: `FactorIds.OTP_EMAIL` - - With SMS OTP: `FactorIds.OTP_PHONE` - - With email magic link: `FactorIds.LINK_EMAIL` - - With SMS magic link: `FactorIds.LINK_PHONE` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/multitenancy" - "github.com/supertokens/supertokens-golang/recipe/multitenancy/multitenancymodels" -) - -func main() { - tenantId := "customer1" - thirdPartyEnabled := true - passwordlessEnabled := true - - // highlight-start - resp, err := multitenancy.CreateOrUpdateTenant(tenantId, multitenancymodels.TenantConfig{ - ThirdPartyEnabled: &thirdPartyEnabled, - PasswordlessEnabled: &passwordlessEnabled, - }) - // highlight-end - - if err != nil { - // handle error - } - if resp.OK.CreatedNew { - // new tenant was created - } else { - // existing tenant's config was modified. - } -} -``` - - - - - - - -```python -from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate -from supertokens_python.recipe.multifactorauth.types import FactorIds - - -async def some_func(): - response = await create_or_update_tenant( - "customer1", - TenantConfigCreateOrUpdate( - first_factors=[ - FactorIds.OTP_PHONE, - FactorIds.OTP_EMAIL, - FactorIds.LINK_PHONE, - FactorIds.LINK_EMAIL, - FactorIds.THIRDPARTY, - ] - ), - ) - - if response.status != "OK": - print("Handle error") - elif response.created_new: - print("new tenant was created") - else: - print("existing tenant's config was modified.") -``` - - - - - -```python -from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate -from supertokens_python.recipe.multifactorauth.types import FactorIds - - -def some_func(): - response = create_or_update_tenant( - "customer1", - TenantConfigCreateOrUpdate( - first_factors=[ - FactorIds.OTP_PHONE, - FactorIds.OTP_EMAIL, - FactorIds.LINK_PHONE, - FactorIds.LINK_EMAIL, - FactorIds.THIRDPARTY, - ] - ), - ) - - if response.status != "OK": - print("Handle error") - elif response.created_new: - print("new tenant was created") - else: - print("existing tenant's config was modified.") -``` - - - - - - - - - - - - - - -```bash -curl --location --request PUT '^{coreInjector_uri_without_quotes}/recipe/multitenancy/tenant/v2' \ ---header 'api-key: ^{coreInjector_api_key_without_quotes}' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "tenantId": "customer1", - "firstFactors": ["otp-email", "otp-phone", "link-email", "link-phone", "thirdparty"] -}' -``` - -In the example above, we have added all factor IDs related to the passwordless recipe. However, you can choose to use a subset of them. The factor IDs are described below: -- ThirdParty: `thirdparty` -- Passwordless: - - With email OTP: `otp-email` - - With SMS OTP: `otp-phone` - - With email magic link: `link-email` - - With SMS magic link: `link-phone` - - - - - -```bash -curl --location --request PUT '^{coreInjector_uri_without_quotes}/recipe/multitenancy/tenant' \ ---header 'api-key: ^{coreInjector_api_key_without_quotes}' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "tenantId": "customer1", - "thirdPartyEnabled": true, - "passwordlessEnabled": true -}' -``` - - - - - - - - - - - - -Create Tenant - - - - - ## Step 2: Configure the third party providers for the tenant @@ -228,174 +36,6 @@ Once again, you can add / modify this config dynamically using our backend SDK o - - - - - -```tsx -import Multiteancy from "supertokens-node/recipe/multitenancy"; - -async function createTenant() { - let resp = await Multiteancy.createOrUpdateThirdPartyConfig("customer1", { - thirdPartyId: "active-directory", - name: "Active Directory", - clients: [{ - clientId: "...", - clientSecret: "...", - }], - oidcDiscoveryEndpoint: "https://login.microsoftonline.com//v2.0/.well-known/openid-configuration", - }); - - if (resp.createdNew) { - // Active Directory added to customer1 - } else { - // Existing active directory config overwritten for customer1 - } -} -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/multitenancy" - "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" -) - -func main() { - tenantId := "customer1" - - // highlight-start - resp, err := multitenancy.CreateOrUpdateThirdPartyConfig(tenantId, tpmodels.ProviderConfig{ - ThirdPartyId: "active-directory", - Name: "Active Directory", - Clients: []tpmodels.ProviderClientConfig{ - { - ClientID: "...", - ClientSecret: "...", - }, - }, - OIDCDiscoveryEndpoint: "https://login.microsoftonline.com//v2.0/.well-known/openid-configuration", - }, nil, nil) - // highlight-end - - if err != nil { - // handle error - } - if resp.OK.CreatedNew { - // Active Directory added to customer1 - } else { - // Existing active directory config overwritten for customer1 - } -} -``` - - - - - - - -```python -from supertokens_python.recipe.multitenancy.asyncio import create_or_update_third_party_config -from supertokens_python.recipe.thirdparty.provider import ProviderConfig, ProviderClientConfig - -async def update_tenant(): - result = await create_or_update_third_party_config( - "customer1", - config=ProviderConfig( - third_party_id="active-directory", - name="Active Directory", - clients=[ - ProviderClientConfig( - client_id="...", - client_secret="...", - ) - ], - oidc_discovery_endpoint="https://login.microsoftonline.com//v2.0/.well-known/openid-configuration", - ), - ) - - if result.status != "OK": - print("Error adding active directory to tenant") - elif result.created_new: - print("Active directory was added to the tenant") - else: - print("Existing tenant's active directory config was modified") -``` - - - - - -```python -from supertokens_python.recipe.multitenancy.syncio import create_or_update_third_party_config -from supertokens_python.recipe.thirdparty.provider import ProviderConfig, ProviderClientConfig - -result = create_or_update_third_party_config( - "customer1", - config=ProviderConfig( - third_party_id="active-directory", - name="Active Directory", - clients=[ - ProviderClientConfig( - client_id="...", - client_secret="...", - ) - ] - ), - ) - -if result.status != "OK": - print("Error creating or updating tenant") -elif result.created_new: - print("New tenant was created") -else: - print("Existing tenant's config was modified") -``` - - - - - - - - - - -```bash -curl --location --request PUT '^{coreInjector_uri_without_quotes}//recipe/multitenancy/config/thirdparty' \ ---header 'api-key: ^{coreInjector_api_key_without_quotes}' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "config": { - "thirdPartyId": "active-directory", - "name": "Active Directory", - "clients": [ - { - "clientId": "...", - "clientSecret": "..." - } - ], - "oidcDiscoveryEndpoint": "https://login.microsoftonline.com//v2.0/.well-known/openid-configuration" - } -}' -``` - - - - - - - -Create Third Party Provider - - - - - - The above code snippet shows how you can add an Active directory login to your tenant. The `clientId`, `clientSecret` and `directoryId` will be provided to you by your tenant. diff --git a/v2/thirdpartypasswordless/common-customizations/multi-tenancy/overview.mdx b/v2/thirdpartypasswordless/common-customizations/multi-tenancy/overview.mdx index d8e091491..7906f90cd 100644 --- a/v2/thirdpartypasswordless/common-customizations/multi-tenancy/overview.mdx +++ b/v2/thirdpartypasswordless/common-customizations/multi-tenancy/overview.mdx @@ -11,22 +11,6 @@ import TabItem from '@theme/TabItem'; - - - -# Multitenant and Enterprise SSO login - -Multitenant login is a feature that lets you customize the login experience for each of your customers. For example, a customer `customer1` hosted on `customer1.yourdomain.com` can have login with `Active Directory` and `Google`, and another customer `customer2` hosted on `customer2.yourdomain.com` can have login with `Okta`, `Facebook` and magic link based login. - -This is also the page that you should see if you want to implement sign in with: -- Okta (`thirdPartyId: "okta"`) -- SAML (`thirdPartyId: "boxy-saml"`) -- Active Directory (`thirdPartyId: "active-directory"`) -- Google Workspaces (`thirdPartyId: "google-workspaces"`) -- GitLab (`thirdPartyId: "gitlab"`) -- Bitbucket (`thirdPartyId: "bitbucket"`) -- Or any other workforce IdP - diff --git a/v2/thirdpartypasswordless/common-customizations/sessions/protecting-frontend-routes.mdx b/v2/thirdpartypasswordless/common-customizations/sessions/protecting-frontend-routes.mdx index 9e5719247..7a0f1c394 100644 --- a/v2/thirdpartypasswordless/common-customizations/sessions/protecting-frontend-routes.mdx +++ b/v2/thirdpartypasswordless/common-customizations/sessions/protecting-frontend-routes.mdx @@ -291,133 +291,6 @@ async function shouldLoadRoute(): Promise { - - - - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```tsx -import Session from 'supertokens-web-js/recipe/session'; - -async function doesSessionExist() { - if (await Session.doesSessionExist()) { - // user is logged in - } else { - // user has not logged in yet - } -} -``` - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```tsx -import Session from 'supertokens-web-js-script/recipe/session'; -async function doesSessionExist() { - if (await Session.doesSessionExist()) { - // user is logged in - } else { - // user has not logged in yet - } -} -``` - - - - - - - - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```tsx -import SuperTokens from 'supertokens-react-native'; - -async function doesSessionExist() { - if (await SuperTokens.doesSessionExist()) { - // user is logged in - } else { - // user has not logged in yet - } -} -``` - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```kotlin -import android.app.Application -import com.supertokens.session.SuperTokens - -class MainApplication: Application() { - fun doesSessionExist() { - if (SuperTokens.doesSessionExist(this.applicationContext)) { - // user is logged in - } else { - // user has not logged in yet - } - } -} -``` - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```swift -import UIKit -import SuperTokensIOS - -fileprivate class ViewController: UIViewController { - func doesSessionExist() { - if SuperTokens.doesSessionExist() { - // User is logged in - } else { - // User is not logged in - } - } -} -``` - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```dart -import 'package:supertokens_flutter/supertokens.dart'; - -Future doesSessionExist() async { - return await SuperTokens.doesSessionExist(); -} -``` - - - - - - - - - ## Verifying the claims of a session diff --git a/v2/thirdpartypasswordless/custom-ui/enable-email-verification.mdx b/v2/thirdpartypasswordless/custom-ui/enable-email-verification.mdx index f5e1e35e5..4f3ade3f4 100644 --- a/v2/thirdpartypasswordless/custom-ui/enable-email-verification.mdx +++ b/v2/thirdpartypasswordless/custom-ui/enable-email-verification.mdx @@ -4,1239 +4,8 @@ title: Enable email verification hide_title: true --- -# Enable email verification +import Redirector from '/src/components/Redirector'; -:::important -- For social / third party logins, the user's email is automatically marked as verified if the user had verified their email to the login provider. -- For passwordless login with email, a user's email is automatically marked as verified when they login. Therefore, the only time this flow would be triggered is if a user changes their email during a session. -::: - - - + -import CustomAdmonition from "/src/components/customAdmonition" -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"; - - -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 - - - - -```tsx -import SuperTokens from "supertokens-node"; -import EmailVerification from "supertokens-node/recipe/emailverification"; -import Session from "supertokens-node/recipe/session"; - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // highlight-start - EmailVerification.init({ - mode: "REQUIRED", // or "OPTIONAL" - }), - // highlight-end - Session.init(), - ], -}); -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/emailverification" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evmodels" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - supertokens.Init(supertokens.TypeInput{ - RecipeList: []supertokens.Recipe{ - // highlight-start - emailverification.Init(evmodels.TypeInput{ - Mode: evmodels.ModeRequired, // or evmodels.ModeOptional - }), - // highlight-end - session.Init(&sessmodels.TypeInput{}), - }, - }) -} -``` - - - - -```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import session -from supertokens_python.recipe import emailverification - -init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore - recipe_list=[ - # highlight-start - emailverification.init(mode='REQUIRED'), # or 'OPTIONAL' - # highlight-end - session.init() - ] -) -``` - - - - - - - - - - -import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" -import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" -import WebJsInjector from "/src/components/webJsInjector" - -## Step 2: Frontend setup - - - - - - - -```tsx -import SuperTokens from "supertokens-web-js"; -import EmailVerification from "supertokens-web-js/recipe/emailverification"; -import Session from "supertokens-web-js/recipe/session"; - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - }, - recipeList: [ - // highlight-start - EmailVerification.init(), - Session.init(), - ], -}); -``` - - - - -Add the following ` -``` - - - -Then call the `supertokensEmailVerification.init` function as shown below - -```tsx -import supertokens from "supertokens-web-js-script"; -import supertokensEmailVerification from "supertokens-web-js-script/recipe/emailverification"; -import supertokensSession from "supertokens-web-js-script/recipe/session"; -supertokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - }, - recipeList: [ - // highlight-start - supertokensEmailVerification.init(), - supertokensSession.init(), - ], -}); -``` - - - - - - - - -:::success -No specific action required here. -::: - - - - - - - - - - -## Step 3: Checking if the user's email is verified in your APIs - -

      If using REQUIRED mode

      - -On the backend, when you initialize the email verification recipe in this mode, the `verifySession` middleware automatically checks if the user's email is verified based on the contents of the session's payload. If the email is not verified, the `verifySession` middleware will return a `403` status code to the client. - -

      If using OPTIONAL mode

      - -In this mode, you need to check if the email is verified yourself in the APIs in which you want this constraint. The verification status should already be in the session's payload. - - - - - - -```tsx -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import express from "express"; -import { SessionRequest } from "supertokens-node/framework/express"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let app = express(); - -app.post( - "/update-blog", - verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - }), - async (req: SessionRequest, res) => { - // All validator checks have passed and the user has a verified email address - } -); -``` - - - -```tsx -import Hapi from "@hapi/hapi"; -import { verifySession } from "supertokens-node/recipe/session/framework/hapi"; -import {SessionRequest} from "supertokens-node/framework/hapi"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let server = Hapi.server({ port: 8000 }); - -server.route({ - path: "/update-blog", - method: "post", - options: { - pre: [ - { - method: verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - }), - }, - ], - }, - handler: async (req: SessionRequest, res) => { - // All validator checks have passed and the user has a verified email address - } -}) -``` - - - -```tsx -import Fastify from "fastify"; -import { verifySession } from "supertokens-node/recipe/session/framework/fastify"; -import { SessionRequest } from "supertokens-node/framework/fastify"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let fastify = Fastify(); - -fastify.post("/update-blog", { - preHandler: verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - }), -}, async (req: SessionRequest, res) => { - // All validator checks have passed and the user has a verified email address -}); -``` - - - - -```tsx -import { verifySession } from "supertokens-node/recipe/session/framework/awsLambda"; -import { SessionEvent } from "supertokens-node/framework/awsLambda"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -async function updateBlog(awsEvent: SessionEvent) { - // All validator checks have passed and the user has a verified email address -}; - -exports.handler = verifySession(updateBlog, { - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] -}); -``` - - - - -```tsx -import KoaRouter from "koa-router"; -import { verifySession } from "supertokens-node/recipe/session/framework/koa"; -import {SessionContext} from "supertokens-node/framework/koa"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let router = new KoaRouter(); - -router.post("/update-blog", verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - }), async (ctx: SessionContext, next) => { - // All validator checks have passed and the user has a verified email address -}); -``` - - - - -```tsx -import { inject, intercept } from "@loopback/core"; -import { RestBindings, MiddlewareContext, post, response } from "@loopback/rest"; -import { verifySession } from "supertokens-node/recipe/session/framework/loopback"; -import Session from "supertokens-node/recipe/session"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -class SetRole { - constructor(@inject(RestBindings.Http.CONTEXT) private ctx: MiddlewareContext) { } - @post("/update-blog") - @intercept(verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - })) - @response(200) - async handler() { - // All validator checks have passed and the user has a verified email address - } -} -``` - - - - -```tsx -import { superTokensNextWrapper } from 'supertokens-node/nextjs' -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import { SessionRequest } from "supertokens-node/framework/express"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -// highlight-start -export default async function setRole(req: SessionRequest, res: any) { - await superTokensNextWrapper( - async (next) => { - await verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - })(req, res, next); - }, - req, - res - ) - // All validator checks have passed and the user has a verified email address -} -``` - - - - -```tsx -import SuperTokens from "supertokens-node"; -import { NextResponse, NextRequest } from "next/server"; -import { withSession } from "supertokens-node/nextjs"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; -// @ts-ignore -import { backendConfig } from "@/app/config/backend"; - -SuperTokens.init(backendConfig()); - -export async function POST(request: NextRequest) { - return withSession(request, async (err, session) => { - if (err) { - return NextResponse.json(err, { status: 500 }); - } - // All validator checks have passed and the user has a verified email address - return NextResponse.json({ message: "Your email is verified!" }); - }, - { - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - } - ); -} -``` - - - - -```tsx -import { Controller, Post, UseGuards, Request, Response, Session } from "@nestjs/common"; -import { SessionContainer, SessionClaimValidator } from "supertokens-node/recipe/session"; -// @ts-ignore -import { AuthGuard } from './auth/auth.guard'; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -@Controller() -export class ExampleController { - @Post('example') - @UseGuards(new AuthGuard({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators: SessionClaimValidator[]) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - })) - async postExample(@Session() session: SessionContainer): Promise { - // All validator checks have passed and the user has a verified email address - return true; - } -} -``` - - - - - - - - -```go -import ( - "net/http" - - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - _ = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - session.VerifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }, exampleAPI).ServeHTTP(rw, r) - }) -} - -func exampleAPI(w http.ResponseWriter, r *http.Request) { - // TODO: session is verified and all validators have passed.. -} -``` - - - - -```go -import ( - "net/http" - - "github.com/gin-gonic/gin" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - router := gin.New() - - // Wrap the API handler in session.VerifySession - router.POST("/likecomment", verifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }), exampleAPI) -} - -// This is a function that wraps the supertokens verification function -// to work the gin -func verifySession(options *sessmodels.VerifySessionOptions) gin.HandlerFunc { - return func(c *gin.Context) { - session.VerifySession(options, func(rw http.ResponseWriter, r *http.Request) { - c.Request = c.Request.WithContext(r.Context()) - c.Next() - })(c.Writer, c.Request) - // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly - c.Abort() - } -} - -func exampleAPI(c *gin.Context) { - // TODO: session is verified and all claim validators pass. -} -``` - - - - -```go -import ( - "net/http" - - "github.com/go-chi/chi" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - r := chi.NewRouter() - - // Wrap the API handler in session.VerifySession - r.Post("/likecomment", session.VerifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }, exampleAPI)) -} - -func exampleAPI(w http.ResponseWriter, r *http.Request) { - // TODO: session is verified and all claim validators pass. -} -``` - - - - -```go -import ( - "net/http" - - "github.com/gorilla/mux" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - router := mux.NewRouter() - - // Wrap the API handler in session.VerifySession - router.HandleFunc("/likecomment", session.VerifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }, exampleAPI)).Methods(http.MethodPost) -} - -func exampleAPI(w http.ResponseWriter, r *http.Request) { - // TODO: session is verified and all claim validators pass. -} -``` - - - - - - - - - -```python -from supertokens_python.recipe.session.framework.fastapi import verify_session -from supertokens_python.recipe.emailverification import EmailVerificationClaim -from supertokens_python.recipe.session import SessionContainer -from fastapi import Depends - -@app.post('/like_comment') # type: ignore -async def like_comment(session: SessionContainer = Depends( - verify_session( - # highlight-start - # We add the EmailVerificationClaim's is_verified validator - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \ - [EmailVerificationClaim.validators.is_verified()] - # highlight-end - ) -)): - # All validator checks have passed and the user has a verified email address - pass -``` - - - - -```python -from supertokens_python.recipe.session.framework.flask import verify_session -from supertokens_python.recipe.emailverification import EmailVerificationClaim - -@app.route('/update-jwt', methods=['POST']) # type: ignore -@verify_session( - # highlight-start - # We add the EmailVerificationClaim's is_verified validator - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \ - [EmailVerificationClaim.validators.is_verified()] - # highlight-end -) -def like_comment(): - # All validator checks have passed and the user has a verified email address - pass -``` - - - - -```python -from supertokens_python.recipe.session.framework.django.asyncio import verify_session -from django.http import HttpRequest -from supertokens_python.recipe.emailverification import EmailVerificationClaim - -@verify_session( - # highlight-start - # We add the EmailVerificationClaim's is_verified validator - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \ - [EmailVerificationClaim.validators.is_verified()] - # highlight-end -) -async def like_comment(request: HttpRequest): - # All validator checks have passed and the user has a verified email address - pass -``` - - - - - - - -We add the SDK's `EmailVerificationClaim` validator to the `verifySession` middleware call as shown above, and that will only allow access if the email is verified, else it will return `403` to the frontend. - - - - - - - -import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" - -## Step 4: Protecting frontend routes - - - - - - - -```tsx -import Session from "supertokens-web-js/recipe/session"; -import { EmailVerificationClaim } from "supertokens-web-js/recipe/emailverification"; - -async function shouldLoadRoute(): Promise { - if (await Session.doesSessionExist()) { - // highlight-start - let validationErrors = await Session.validateClaims(); - - if (validationErrors.length === 0) { - // user has verified their email address - return true; - } else { - for (const err of validationErrors) { - if (err.id === EmailVerificationClaim.id) { - // email is not verified - } - } - } - // highlight-end - } - // a session does not exist, or email is not verified - return false -} -``` - - - - -```tsx -import supertokensSession from "supertokens-web-js-script/recipe/session"; -import supertokensEmailVerification from "supertokens-web-js-script/recipe/emailverification"; -async function shouldLoadRoute(): Promise { - if (await supertokensSession.doesSessionExist()) { - // highlight-start - let validationErrors = await supertokensSession.validateClaims(); - - if (validationErrors.length === 0) { - // user has verified their email address - return true; - } else { - for (const err of validationErrors) { - if (err.id === supertokensEmailVerification.EmailVerificationClaim.id) { - // email is not verified - } - } - } - // highlight-end - } - // a session does not exist, or email is not verified - return false -} -``` - - - - -In your protected routes, you need to first check if a session exists, and then call the `Session.validateClaims` function as shown above. This function inspects the session's contents and runs claim validators on them. If a claim validator fails, it will be reflected in the `validationErrors` variable. The `EmailVerificationClaim` validator will be automatically checked by this function since you have initialized the email verification recipe. - -
      Handling 403 responses on the frontend - - - -If your frontend queries a protected API on your backend and it fails with a 403, you can call the `validateClaims` function and loop through the errors to know which claim has failed: - -```tsx -import axios from "axios"; -import Session from "supertokens-web-js/recipe/session"; -import { EmailVerificationClaim, sendVerificationEmail } from "supertokens-web-js/recipe/emailverification"; - -async function callProtectedRoute() { - try { - let response = await axios.get("^{form_apiDomain}/protectedroute"); - } catch (error) { - // highlight-start - if (axios.isAxiosError(error) && error.response?.status === 403) { - let validationErrors = await Session.validateClaims(); - for (let err of validationErrors) { - if (err.id === EmailVerificationClaim.id) { - // email verification claim check failed - // We call the sendEmail function defined in the next section to send the verification email. - // await sendEmail(); - } else { - // some other claim check failed (from the global validators list) - } - } - // highlight-end - - } - } -} -``` - - - -
      - -
      - - - - - - - -```tsx -import SuperTokens from 'supertokens-react-native'; - -async function checkIfEmailIsVerified() { - if (await SuperTokens.doesSessionExist()) { - - // highlight-start - let isVerified: boolean = (await SuperTokens.getAccessTokenPayloadSecurely())["st-ev"].v; - - if (isVerified) { - // TODO.. - } else { - // TODO.. - } - // highlight-end - } -} -``` - - - - - -```kotlin -import android.app.Application -import com.supertokens.session.SuperTokens -import org.json.JSONObject - -class MainApplication: Application() { - fun checkIfEmailIsVerified() { - val accessTokenPayload: JSONObject = SuperTokens.getAccessTokenPayloadSecurely(this); - val isVerified: Boolean = (accessTokenPayload.get("st-ev") as JSONObject).get("v") as Boolean - if (isVerified) { - // TODO.. - } else { - // TODO.. - } - } -} -``` - - - - - -```swift -import UIKit -import SuperTokensIOS - -fileprivate class ViewController: UIViewController { - func checkIfEmailIsVerified() { - if let accessTokenPayload: [String: Any] = try? SuperTokens.getAccessTokenPayloadSecurely(), let emailVerificationObject: [String: Any] = accessTokenPayload["st-ev"] as? [String: Any], let isVerified: Bool = emailVerificationObject["v"] as? Bool { - if isVerified { - // Email is verified - } else { - // Email is not verified - } - } - } -} -``` - - - - - -```dart -import 'package:supertokens_flutter/supertokens.dart'; - -Future checkIfEmailIsVerified() async { - var accessTokenPayload = await SuperTokens.getAccessTokenPayloadSecurely(); - - if (accessTokenPayload.containsKey("st-ev")) { - Map emailVerificationObject = accessTokenPayload["st-ev"]; - - if (emailVerificationObject.containsKey("v")) { - bool isVerified = emailVerificationObject["v"]; - - if (isVerified) { - // Email is verified - } else { - // Email is not verified - } - } - } -} -``` - - - - - -
      Handling 403 responses on the frontend - -If your frontend queries a protected API on your backend and it fails with a 403, you can check the value of the `st-ev` claim in the access token payload. If it is set to false you can send the verification email - -
      - -
      -
      - -import AppInfoForm from "/src/components/appInfoForm" - -## Step 5: Sending the email verification email - -When the email verification validators fail, or post sign up, you want to redirect the user to a screen telling them that a verification email has been sent to them. On this screen, you should call the following API - - - - - - - -```tsx -import { sendVerificationEmail } from "supertokens-web-js/recipe/emailverification"; - -async function sendEmail() { - try { - let response = await sendVerificationEmail(); - if (response.status === "EMAIL_ALREADY_VERIFIED_ERROR") { - // This can happen if the info about email verification in the session was outdated. - // Redirect the user to the home page - window.location.assign("/home"); - } else { - // email was sent successfully. - window.alert("Please check your email and click the link in it") - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - -```tsx -import supertokensEmailVerification from "supertokens-web-js/recipe/emailverification"; - -async function sendEmail() { - try { - let response = await supertokensEmailVerification.sendVerificationEmail(); - if (response.status === "EMAIL_ALREADY_VERIFIED_ERROR") { - // This can happen if the info about email verification in the session was outdated. - // Redirect the user to the home page - window.location.assign("/home"); - } else { - // email was sent successfully. - window.alert("Please check your email and click the link in it") - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - - - - - - - -You should create a new screen on your app that asks the user to enter their email to which an email will be sent. This screen should ideally be linked to from the sign in form. - -Once the user has enters their email, you can call the following API to send an email verification email to that user: - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/user/email/verify/token' \ ---header 'Authorization: Bearer ...' -``` - - - -The response body from the API call has a `status` property in it: -- `status: "OK"`: An email was sent to the user successfully. -- `status: "EMAIL_ALREADY_VERIFIED_ERROR"`: This status can be returned if the info about email verification in the session was outdated. Redirect the user to the home page. -- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. - - - -You do not need to add the tenant ID to the path here because the backend fetches the tenantId of the user from the session token. - - - - - - -:::note -The API for sending an email verification email requires an active session. If you are using our frontend SDKs, then the session tokens should automatically get attached to the request. -::: - -### Changing the email verification link domain / path -By default, the email verification link will point to the `websiteDomain` that is configured on the backend, on the `/auth/verify-email` route (where `/auth` is the default value of `websiteBasePath`). - -If you want to change this to a different path, a different domain, or deep link it to your mobile / desktop app, then you can do so on the backend in the following way: - - - - -```tsx -import SuperTokens from "supertokens-node"; -import EmailVerification from "supertokens-node/recipe/emailverification"; - -SuperTokens.init({ - supertokens: { - connectionURI: "...", - }, - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "..." - }, - recipeList: [ - EmailVerification.init({ - mode: "OPTIONAL", - // highlight-start - emailDelivery: { - override: (originalImplementation) => { - return { - ...originalImplementation, - sendEmail(input) { - return originalImplementation.sendEmail({ - ...input, - emailVerifyLink: input.emailVerifyLink.replace( - // This is: `${websiteDomain}${websiteBasePath}/verify-email` - "http://localhost:3000/auth/verify-email", - "http://localhost:3000/your/path" - ) - } - ) - }, - } - } - } - // highlight-end - }) - ] -}); -``` - - - -```go -import ( - "strings" - - "github.com/supertokens/supertokens-golang/ingredients/emaildelivery" - "github.com/supertokens/supertokens-golang/recipe/emailverification" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - supertokens.Init(supertokens.TypeInput{ - RecipeList: []supertokens.Recipe{ - emailverification.Init(evmodels.TypeInput{ - Mode: evmodels.ModeOptional, - // highlight-start - EmailDelivery: &emaildelivery.TypeInput{ - Override: func(originalImplementation emaildelivery.EmailDeliveryInterface) emaildelivery.EmailDeliveryInterface { - ogSendEmail := *originalImplementation.SendEmail - - (*originalImplementation.SendEmail) = func(input emaildelivery.EmailType, userContext supertokens.UserContext) error { - // This is: `${websiteDomain}${websiteBasePath}/verify-email` - input.EmailVerification.EmailVerifyLink = strings.Replace( - input.EmailVerification.EmailVerifyLink, - "http://localhost:3000/auth/verify-email", - "http://localhost:3000/your/path", 1, - ) - return ogSendEmail(input, userContext) - } - return originalImplementation - }, - }, - // highlight-end - }), - }, - }) -} -``` - - - -```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import emailverification -from supertokens_python.ingredients.emaildelivery.types import EmailDeliveryConfig -from supertokens_python.recipe.emailverification.types import EmailDeliveryOverrideInput, EmailTemplateVars -from typing import Dict, Any - - -def custom_email_delivery(original_implementation: EmailDeliveryOverrideInput) -> EmailDeliveryOverrideInput: - original_send_email = original_implementation.send_email - - # highlight-start - async def send_email(template_vars: EmailTemplateVars, user_context: Dict[str, Any]) -> None: - - # This is: `${websiteDomain}${websiteBasePath}/verify-email` - template_vars.email_verify_link = template_vars.email_verify_link.replace( - "http://localhost:3000/auth/verify-email", "http://localhost:3000/your/path") - - return await original_send_email(template_vars, user_context) - # highlight-end - - original_implementation.send_email = send_email - return original_implementation - - -init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore - recipe_list=[ - emailverification.init( - mode="OPTIONAL", - # highlight-next-line - email_delivery=EmailDeliveryConfig(override=custom_email_delivery)) - ] -) -``` - - - - - - - -For a multi tenant setup, the input to the `sendEmail` function will also contain the `tenantId`. You can use this to determine the correct value to set for the websiteDomain in the generated link. - - - -## Step 6: Verifying the email post link clicked - -Once the user clicks the email verification link, and it opens your app, you can call the following function which will automatically extract the token and tenantId (if using a multi tenant setup) from the link and call the token verification API. - - - - - - - -```tsx -import { verifyEmail } from "supertokens-web-js/recipe/emailverification"; - -async function consumeVerificationCode() { - try { - let response = await verifyEmail(); - if (response.status === "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR") { - // This can happen if the verification code is expired or invalid. - // You should ask the user to retry - window.alert("Oops! Seems like the verification link expired. Please try again") - window.location.assign("/auth/verify-email") // back to the email sending screen. - } else { - // email was verified successfully. - window.location.assign("/home") - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - -```tsx -import supertokensEmailVerification from "supertokens-web-js/recipe/emailverification"; - -async function consumeVerificationCode() { - try { - let response = await supertokensEmailVerification.verifyEmail(); - if (response.status === "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR") { - // This can happen if the verification code is expired or invalid. - // You should ask the user to retry - window.alert("Oops! Seems like the verification link expired. Please try again") - window.location.assign("/auth/verify-email") // back to the email sending screen. - } else { - // email was verified successfully. - window.location.assign("/home") - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - - - - - - - -Once the user clicks the email verification link, and it opens as a deep link into your mobile app, you can extract the token from the link and call the verification API as shown below: - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/user/email/verify' \ ---header 'Content-Type: application/json; charset=utf-8' \ ---data-raw '{ - "method": "token", - "token": "ZTRiOTBjNz...jI5MTZlODkxw" -}' -``` - - - - -For a multi tenancy setup, the `` value can be fetched from `tenantId` query parameter from the email verification link. If it's not there in the link, you can use the value `"public"` (which is the default tenant). - - - - - - -The response body from the API call has a `status` property in it: -- `status: "OK"`: Email verification was successful. -- `status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR"`: This can happen if the verification code is expired or invalid. You should ask the user to retry. -- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. - - - - -:::caution -- This API doesn't require an active session to succeed. -- If you are calling the above API on page load, there is an edge case in which email clients might open the verification link in the email (for scanning purposes) and consume the token in the URL. This would lead to issues in which an attacker could sign up using someone else's email and end up with a veriifed status! - - To prevent this, on page load, you should check if a session exists, and if it does, only then call the above API. If a session does not exist, you should first show a button, which when clicked would call the above API (email clients won't automatically click on this button). The button text could be something like "Click here to verify your email". -::: - - - - - - - -## See also - -- [Post email verification action](../common-customizations/email-verification/handling-email-verification-success) -- [Change email verification link's lifetime](../common-customizations/email-verification/changing-token-lifetime) -- [Customise email template or email delivery method](../email-delivery/about) -- [Manually changing email verification status for a user](../common-customizations/email-verification/changing-email-verification-status) -- [Generating email verification links manually](../common-customizations/email-verification/generate-link-manually) -- 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) in `REQUIRED` mode. - - \ No newline at end of file diff --git a/v2/thirdpartypasswordless/custom-ui/handling-session-tokens.mdx b/v2/thirdpartypasswordless/custom-ui/handling-session-tokens.mdx index bdfa1a21f..6ff934e75 100644 --- a/v2/thirdpartypasswordless/custom-ui/handling-session-tokens.mdx +++ b/v2/thirdpartypasswordless/custom-ui/handling-session-tokens.mdx @@ -4,412 +4,7 @@ title: Handling session tokens hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -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"; -import AppInfoForm from "/src/components/appInfoForm" -import {Question, Answer}from "/src/components/question" + -# Handling session tokens - -There are two modes ways in which you can use sessions with SuperTokens: -- Using `httpOnly` cookies -- Authorization bearer token. - -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 - -### For Web - - - - - -:::success -No action required. -::: - -Our frontend SDK handles everything for you. You only need to make sure that you have called `supertokens.init` before making any network requests. - -Our SDK adds interceptors to `fetch` and `XHR` (used by `axios`) to save and add session tokens from and to the request. - -By default, our web SDKs use cookies to provide credentials. - - - - - -import NetworkInterceptors from "/session/reusableMD/networkInterceptors.mdx" - -### For React-Native -Our frontend SDK handles everything for you. You only need to make sure that you have added our network interceptors as shown below - - - -:::note -By default our mobile SDKs use a bearer token in the Authorization header to provide credentials. -::: - -### For Android - - - - -```kotlin -import android.app.Application -import com.supertokens.session.SuperTokens -import com.supertokens.session.SuperTokensHttpURLConnection -import com.supertokens.session.SuperTokensPersistentCookieStore -import java.net.URL -import java.net.HttpURLConnection - -class MainApplication: Application() { - override fun onCreate() { - super.onCreate() - // TODO: Make sure to call SuperTokens.init - } - - fun makeRequest() { - val url = URL("") - val connection = SuperTokensHttpURLConnection.newRequest(url, object: SuperTokensHttpURLConnection.PreConnectCallback { - override fun doAction(con: HttpURLConnection?) { - // TODO: Use `con` to set request method, headers etc - } - }) - - // Handle response using connection object, for example: - if (connection.responseCode == 200) { - // TODO: implement - } - } -} -``` - -:::note -When making network requests you do not need to call `HttpURLConnection.connect` because SuperTokens does this for you. -::: - - - - -```kotlin -import android.content.Context -import com.supertokens.session.SuperTokens -import com.supertokens.session.SuperTokensInterceptor -import okhttp3.OkHttpClient -import retrofit2.Retrofit - -class NetworkManager { - fun getClient(context: Context): OkHttpClient { - val clientBuilder = OkHttpClient.Builder() - clientBuilder.addInterceptor(SuperTokensInterceptor()) - // TODO: Make sure to call SuperTokens.init - - val client = clientBuilder.build() - - // REQUIRED FOR RETROFIT ONLY - val instance = Retrofit.Builder() - .baseUrl("") - .client(client) - .build() - - return client - } - - fun makeRequest(context: Context) { - val client = getClient(context) - // Use client to make requests normally - } -} -``` - - - - -:::note -By default our mobile SDKs use a bearer token in the Authorization header to provide credentials. -::: - -### For iOS - - - - - -

      Using URLSession.shared

      - -```swift -import Foundation -import SuperTokensIOS - -fileprivate class NetworkManager { - func setupSuperTokensInterceptor() { - URLProtocol.registerClass(SuperTokensURLProtocol.self) - } -} -``` - -

      Using a custom URLSession instance

      - -```swift -import Foundation -import SuperTokensIOS - -fileprivate class NetworkManager { - func setupSuperTokensInterceptor() { - let configuration = URLSessionConfiguration.default - configuration.protocolClasses = [SuperTokensURLProtocol.self] - let session = URLSession(configuration: configuration) - - // Use session when making network requests - } -} -``` - -
      - - - -```swift -import Foundation -import SuperTokensIOS -import Alamofire - -fileprivate class NetworkManager { - func setupSuperTokensInterceptor() { - let configuration = URLSessionConfiguration.af.default - configuration.protocolClasses = [SuperTokensURLProtocol.self] + (configuration.protocolClasses ?? []) - let session = Session(configuration: configuration) - - // Use session when making network requests - } -} -``` - - - -
      - -:::note -By default our mobile SDKs use a bearer token in the Authorization header to provide credentials. -::: - -### For Flutter - - - - -You can make requests as you normally would with `http`, the only difference is that you import the client from the supertokens package instead. - -```dart -// Import http from the SuperTokens package -import 'package:supertokens_flutter/http.dart' as http; - -Future makeRequest() async { - Uri uri = Uri.parse("http://localhost:3001/api"); - var response = await http.get(uri); - // handle response -} -``` - -

      Using a custom http client

      - -If you use a custom http client and want to use SuperTokens, you can simply provide the SDK with your client. All requests will continue to use your client along with the session logic that SuperTokens provides. - -```dart -// Import http from the SuperTokens package -import 'package:supertokens_flutter/http.dart' as http; - -Future makeRequest() async { - Uri uri = Uri.parse("http://localhost:3001/api"); - - // Initialise your custom client - var customClient = http.Client(); - // provide your custom client to SuperTokens - var httpClient = http.Client(client: customClient); - - var response = await httpClient.get(uri); - // handle response -} -``` - -
      - - -

      Add the SuperTokens interceptor

      - -Use the extension method provided by the SuperTokens SDK to enable interception on your Dio client. This allows the SuperTokens SDK to handle session tokens for you. - -```dart -import 'package:supertokens_flutter/dio.dart'; -import 'package:dio/dio.dart'; - -void setup() { - Dio dio = Dio(); // Create a Dio instance. - dio.addSupertokensInterceptor(); -} -``` - -

      Making network requests

      - -You can make requests as you normally would with `dio`. - -```dart -import 'package:supertokens_flutter/dio.dart'; -import 'package:dio/dio.dart'; - -void setup() { - Dio dio = Dio( - // Provide your config here - ); - dio.addSupertokensInterceptor(); - - var response = dio.get("http://localhost:3001/api"); - // handle response -} -``` - -
      -
      - -:::note -By default our mobile SDKs use a bearer token in the Authorization header to provide credentials. -::: - -## If not using our frontend SDK - -:::caution -We highly recommend using our frontend SDK to handle session token management. It will save you a lot of time. -::: - -In this case, you will need to manually handle the tokens and session refreshing, and decide if you are going to use header or cookie-based sessions. - -For browsers, we recommend cookies, while for mobile apps (or if you don't want to use the built-in cookie manager) you should use header-based sessions. - - - - - -### On login - -You should attach the `st-auth-mode` header to calls to the login API, but this header is safe to attach to all requests. In this case it should be set to "cookie". - -The login API will return the following headers: -- `Set-Cookie`: This will contain the `sAccessToken`, `sRefreshToken` cookies which will be `httpOnly` and will be automatically mananaged by the browser. For mobile apps, you will need to setup cookie handling yourself, use our SDK or use a header based authentication mode. -- `front-token` header: This contains information about the access token: - - The userID - - The expiry time of the access token - - The payload added by you in the access token. - - Here is the structure of the token: - ```tsx - let frontTokenFromRequestHeader = "..."; - let frontTokenDecoded = JSON.parse(decodeURIComponent(escape(atob(frontTokenFromRequestHeader)))); - console.log(frontTokenDecoded); - /* - { - ate: 1665226412455, // time in milliseconds for when the access token will expire, and then a refresh is required - uid: "....", // user ID - up: { - sub: "..", - iat: .., - ... // other access token payload - } - } - - */ - ``` - - This token is mainly used for cookie based auth because you don't have access to the actual access token on the frontend (for security reasons), but may want to read its payload (for example to know the user's role). This token itself is not signed and hence can't be used in place of the access token itself. You may want to save this token in localstorage or in frontend cookies (using `document.cookies`). - -- `anti-csrf` header (optional): By default it's not required, so it's not sent. But if this is sent, you should save this token as well for use when making requests. - -### Making network requests to protected APIs - -The `sAccessToken` will get attached to the request automatically by the browser. Other than that, you need to add the following headers to the request: -- `rid: "anti-csrf"` - this prevents against anti-CSRF requests. If your `apiDomain` and `websiteDomain` values are exactly the same, then this is not necessary. -- `anti-csrf` header (optional): If this was provided to you during login, then you need to add that token as the value of this header. -- You need to set the `credentials` header to `true` or `include`. This is achieved different based on the library you are using to make requests. - -An API call can potentially update the `sAccessToken` and `front-token` tokens, for example if you call the `mergeIntoAccessTokenPayload` function on the `session` object on the backend. This kind of update is reflected in the response headers for your API calls. The headers will contain new values for: -- `sAccessToken`: This will be as a new `Set-Cookie` header and will be managed by the browser automatically. -- `front-token`: This should be read and saved by you in the same way as it's being done during login. - -### Handling session refreshing -If any of your API calls return with a status code of `401`, it means that the access token has expired. This will require you to refresh the session before retrying the same API call. - -You can call the refresh API as follows: - - - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/session/refresh' \ ---header 'Cookie: sRefreshToken=...' -``` - -:::note -- You may also need to add the `anti-csrf` header to the request if that was provided to you during sign in. -- The cURL command above shows the `sRefreshToken` cookie as well, but this is added by the web browser automatically, so you don't need to add it explicitly. -::: - - - -The result of a session refresh will be either: -- Status code `200`: This implies a succesful refresh. The set of tokens returned here will be the same as when the user logs in, so you can handle them in the same way. -- Status code `401`: This means that the refresh token is invalid, or has been revoked. You must ask the user to relogin. Remember to clear the `front-token` that you saved on the frontend earlier. - - - - - -### On login - -You should attach the `st-auth-mode` header to calls to the login API, but this header is safe to attach to all requests. In this case it should be set to "header". - -The login API will return the following headers: -- `st-access-token`: This contains the current access token associated with the session. You should save this in your application (e.g., in frontend localstorage). -- `st-refresh-token`: This contains the current refresh token associated with the session. You should save this in your application (e.g., in frontend localstorage). - -### Making network requests to protected APIs - -You need to add the following headers to request: -- `authorization: Bearer {access-token}` -- You need to set the `credentials` to `true` or `include`. This is achieved different based on the library you are using to make requests. - -An API call can potentially update the `access-token`, for example if you call the `mergeIntoAccessTokenPayload` function on the `session` object on the backend. This kind of update is reflected in the response headers for your API calls. The headers will contain new values for `st-access-token` - -These should be read and saved by you in the same way as it's being done during login. - -### Handling session refreshing -If any of your API calls return with a status code of `401`, it means that the access token has expired. This will require you to refresh the session before retrying the same API call. - -You can call the refresh API as follows: - - - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/session/refresh' \ ---header 'authorization: Bearer {refresh-token}' -``` - - - -The result of a session refresh will be either: -- Status code `200`: This implies a succesful refresh. The set of tokens returned here will be the same as when the user logs in, so you can handle them in the same way. -- Status code `401`: This means that the refresh token is invalid, or has been revoked. You must ask the user to relogin. Remember to clear the `st-refresh-token` and `st-access-token` that you saved on the frontend earlier. - - - - diff --git a/v2/thirdpartypasswordless/custom-ui/init/backend.mdx b/v2/thirdpartypasswordless/custom-ui/init/backend.mdx index ae79f00c6..577fc5a32 100644 --- a/v2/thirdpartypasswordless/custom-ui/init/backend.mdx +++ b/v2/thirdpartypasswordless/custom-ui/init/backend.mdx @@ -4,1232 +4,8 @@ title: "Step 2: Backend" hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import TabItem from '@theme/TabItem'; -import NodeJSFrameworkSubTabsServerless from "/src/components/tabs/NodeJSFrameworkSubTabsServerless" -import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs" -import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" -import AppInfoForm from "/src/components/appInfoForm" -import { PasswordlessBackendForm } from "/src/components/snippetConfigForm/passwordlessBackendForm"; -import CoreInjector from "/src/components/coreInjector" -import { Question, Answer }from "/src/components/question" -import BackendSDKCasing from "/src/components/BackendSDKCasing" -import BackendDeliveryMethod from "../../../passwordless/reusableMD/backendDeliveryMethod.mdx" -# Backend Integration + -## Supported frameworks - -import TechStackSupport from "../../../community/reusableMD/supported-tech-stacks-backend.mdx" - - - -## 1) Install - - - - -```bash -npm i -s supertokens-node -``` - - - - -```bash -go get github.com/supertokens/supertokens-golang -``` - - - - -```bash -pip install supertokens-python -``` - - - - -## 2) Initialise SuperTokens - - - - - - -Add the code below to your server's init file. - - - - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import ThirdParty from "supertokens-node/recipe/thirdparty" -import Passwordless from "supertokens-node/recipe/passwordless" - -supertokens.init({ - framework: "express", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - Passwordless.init({ - flowType: "^{form_flowType}", - contactMethod: "^{form_contactMethod}" - }), - ThirdParty.init(/*TODO: See next steps for third party provider setup */), - Session.init() // initializes session features - ] -}); -``` - - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import ThirdParty from "supertokens-node/recipe/thirdparty" -import Passwordless from "supertokens-node/recipe/passwordless" - -supertokens.init({ - framework: "hapi", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - Passwordless.init({ - flowType: "^{form_flowType}", - contactMethod: "^{form_contactMethod}" - }), - ThirdParty.init(/*TODO: See next steps for third party provider setup */), - Session.init() // initializes session features - ] -}); -``` - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import ThirdParty from "supertokens-node/recipe/thirdparty" -import Passwordless from "supertokens-node/recipe/passwordless" - -supertokens.init({ - framework: "fastify", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - Passwordless.init({ - flowType: "^{form_flowType}", - contactMethod: "^{form_contactMethod}" - }), - ThirdParty.init(/*TODO: See next steps for third party provider setup */), - Session.init() // initializes session features - ] -}); -``` - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import ThirdParty from "supertokens-node/recipe/thirdparty" -import Passwordless from "supertokens-node/recipe/passwordless" - -supertokens.init({ - framework: "koa", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - Passwordless.init({ - flowType: "^{form_flowType}", - contactMethod: "^{form_contactMethod}" - }), - ThirdParty.init(/*TODO: See next steps for third party provider setup */), - Session.init() // initializes session features - ] -}); -``` - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import ThirdParty from "supertokens-node/recipe/thirdparty" -import Passwordless from "supertokens-node/recipe/passwordless" - -supertokens.init({ - framework: "loopback", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - Passwordless.init({ - flowType: "^{form_flowType}", - contactMethod: "^{form_contactMethod}" - }), - ThirdParty.init(/*TODO: See next steps for third party provider setup */), - Session.init() // initializes session features - ] -}); -``` - - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/passwordless" - "github.com/supertokens/supertokens-golang/recipe/passwordless/plessmodels" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/thirdparty" - "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - apiBasePath := "^{form_apiBasePath}" - websiteBasePath := "^{form_websiteBasePath}" - err := supertokens.Init(supertokens.TypeInput{ - Supertokens: &supertokens.ConnectionInfo{ - ^{coreInjector_connection_uri_comment} - ConnectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}APIKey: ^{coreInjector_api_key}, - }, - AppInfo: supertokens.AppInfo{ - AppName: "^{form_appName}", - APIDomain: "^{form_apiDomain}", - WebsiteDomain: "^{form_websiteDomain}", - APIBasePath: &apiBasePath, - WebsiteBasePath: &websiteBasePath, - }, - RecipeList: []supertokens.Recipe{ - passwordless.Init(plessmodels.TypeInput{ - FlowType: "^{form_flowType}", - ^{form_contactMethod_sendCB_Go} - }), - thirdparty.Init(&tpmodels.TypeInput{ - SignInAndUpFeature: tpmodels.TypeInputSignInAndUp{ - /*TODO: See next steps for third party provider setup */ - }, - }), - session.Init(nil), // initializes session features - }, - }) - - if err != nil { - panic(err.Error()) - } -} -``` - - - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig -from supertokens_python.recipe import thirdparty, passwordless, session - -^{form_contactMethod_import_Python} -init( - app_info=InputAppInfo( - app_name="^{form_appName}", - api_domain="^{form_apiDomain}", - website_domain="^{form_websiteDomain}", - api_base_path="^{form_apiBasePath}", - website_base_path="^{form_websiteBasePath}" - ), - supertokens_config=SupertokensConfig( - ^{coreInjector_connection_uri_comment_with_hash} - connection_uri=^{coreInjector_uri} - ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} - ), - framework='fastapi', - recipe_list=[ - session.init(), # initializes session features - thirdparty.init( - # TODO: See next steps for third party provider setup - ), - passwordless.init( - flow_type="^{form_flowType}", - contact_config=^{form_contactMethod_initialize_Python}() - ) - ], - mode='asgi' # use wsgi if you are running using gunicorn -) -``` - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig -from supertokens_python.recipe import thirdparty, passwordless, session - -^{form_contactMethod_import_Python} -init( - app_info=InputAppInfo( - app_name="^{form_appName}", - api_domain="^{form_apiDomain}", - website_domain="^{form_websiteDomain}", - api_base_path="^{form_apiBasePath}", - website_base_path="^{form_websiteBasePath}" - ), - supertokens_config=SupertokensConfig( - ^{coreInjector_connection_uri_comment_with_hash} - connection_uri=^{coreInjector_uri} - ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} - ), - framework='flask', - recipe_list=[ - session.init(), # initializes session features - thirdparty.init( - # TODO: See next steps for third party provider setup - ), - passwordless.init( - flow_type="^{form_flowType}", - contact_config=^{form_contactMethod_initialize_Python}() - ) - ] -) -``` - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig -from supertokens_python.recipe import thirdparty, passwordless, session - -^{form_contactMethod_import_Python} -init( - app_info=InputAppInfo( - app_name="^{form_appName}", - api_domain="^{form_apiDomain}", - website_domain="^{form_websiteDomain}", - api_base_path="^{form_apiBasePath}", - website_base_path="^{form_websiteBasePath}" - ), - supertokens_config=SupertokensConfig( - ^{coreInjector_connection_uri_comment_with_hash} - connection_uri=^{coreInjector_uri} - ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} - ), - framework='django', - recipe_list=[ - session.init(), # initializes session features - thirdparty.init( - # TODO: See next steps for third party provider setup - ), - passwordless.init( - flow_type="^{form_flowType}", - contact_config=^{form_contactMethod_initialize_Python}() - ) - ], - mode='asgi' # use wsgi if you are running django server in sync mode -) -``` - - - - - - - - - - - - -## 3) Initialise Social login providers - - - - -Populate the `providers` array with the third party auth providers you want. - - - - -```tsx -import SuperTokens from "supertokens-node"; -import ThirdParty from "supertokens-node/recipe/thirdparty" -import Passwordless from "supertokens-node/recipe/passwordless" - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - //highlight-start - Passwordless.init({ - flowType: "^{form_flowType}", - contactMethod: "^{form_contactMethod}", - }), - ThirdParty.init({ - // We have provided you with development keys which you can use for testing. - // IMPORTANT: Please replace them with your own OAuth keys for production use. - signInAndUpFeature: { - providers: [{ - config: { - thirdPartyId: "google", - clients: [{ - clientId: "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", - clientSecret: "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" - }] - } - }, { - config: { - thirdPartyId: "github", - clients: [{ - clientId: "467101b197249757c71f", - clientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd" - }] - } - }, { - config: { - thirdPartyId: "apple", - clients: [{ - clientId: "4398792-io.supertokens.example.service", - additionalConfig: { - keyId: "7M48Y4RYDL", - privateKey: - "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----", - teamId: "YWQCXGJRJL", - } - }] - } - }], - } - //highlight-end - }), - // ... - ] -}); -``` - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/thirdparty" - "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" -) - -func main() { - - // Inside supertokens.Init -> RecipeList - thirdparty.Init(&tpmodels.TypeInput{ - // highlight-start - SignInAndUpFeature: tpmodels.TypeInputSignInAndUp{ - Providers: []tpmodels.ProviderInput{ - // We have provided you with development keys which you can use for testing. - // IMPORTANT: Please replace them with your own OAuth keys for production use. - { - Config: tpmodels.ProviderConfig{ - ThirdPartyId: "google", - Clients: []tpmodels.ProviderClientConfig{ - { - ClientID: "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", - ClientSecret: "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", - }, - }, - }, - }, - { - Config: tpmodels.ProviderConfig{ - ThirdPartyId: "github", - Clients: []tpmodels.ProviderClientConfig{ - { - ClientID: "467101b197249757c71f", - ClientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd", - }, - }, - }, - }, - { - Config: tpmodels.ProviderConfig{ - ThirdPartyId: "apple", - Clients: []tpmodels.ProviderClientConfig{ - { - ClientID: "4398792-io.supertokens.example.service", - AdditionalConfig: map[string]interface{}{ - "keyId": "7M48Y4RYDL", - "privateKey": "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----", - "teamId": "YWQCXGJRJL", - }, - }, - }, - }, - }, - }, - }, - // highlight-end - }) -} -``` - - - -```python -from supertokens_python.recipe.thirdparty.provider import ProviderInput, ProviderConfig, ProviderClientConfig -from supertokens_python.recipe import thirdparty - -# Inside init -thirdparty.init( - # highlight-start - sign_in_and_up_feature=thirdparty.SignInAndUpFeature( - providers=[ - # We have provided you with development keys which you can use for testing. - # IMPORTANT: Please replace them with your own OAuth keys for production use. - ProviderInput( - config=ProviderConfig( - third_party_id="google", - clients=[ - ProviderClientConfig( - client_id="1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", - client_secret="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", - ), - ], - ), - ), - ProviderInput( - config=ProviderConfig( - third_party_id="github", - clients=[ - ProviderClientConfig( - client_id="467101b197249757c71f", - client_secret="e97051221f4b6426e8fe8d51486396703012f5bd", - ) - ], - ), - ), - ProviderInput( - config=ProviderConfig( - third_party_id="apple", - clients=[ - ProviderClientConfig( - client_id="io.supertokens.example.service", - additional_config={ - "keyId": "7M48Y4RYDL", - "privateKey": "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----", - "teamId": "YWQCXGJRJL" - }, - ), - ], - ), - ), - ] - ) - # highlight-end -) -``` - - - - -**When you want to generate your own keys**, please refer to the corresponding documentation to get your client ids and client secrets for each of the below providers: - -
      -Google - -- Generate your client ID and secret by following the [docs here](https://support.google.com/cloud/answer/6158849?hl=en) -- Set the authorisation callback URL to `^{form_websiteDomain}^{form_websiteBasePathForCallbacks}/callback/google` - -
      - -
      -Github - -- Generate your client ID and secret by following the [docs here](https://docs.github.com/en/developers/apps/creating-an-oauth-app) -- Set the authorisation callback URL to `^{form_websiteDomain}^{form_websiteBasePathForCallbacks}/callback/github` - -
      - -
      -Apple - -- Generate your client ID and secret by following [this article](https://medium.com/identity-beyond-borders/how-to-configure-sign-in-with-apple-77c61e336003) -- Set the authorisation callback URL to `^{form_apiDomain}^{form_apiBasePathForCallbacks}/callback/apple`. Note that Apple doesn't allow `localhost` in the URL. So if you are in dev mode, you can use the dev keys we have provided above. - -
      - -
      - -:::important -You can find the list of built in providers [here](../../common-customizations/signup-form/built-in-providers). To add a provider that is not listed, you can follow our guide on [setting up custom providers](../../common-customizations/signup-form/custom-providers). -::: - -
      - -## 4) Add the SuperTokens APIs & CORS setup - - - - - - - - -:::important -- Add the `middleware` BEFORE all your routes. -- Add the `cors` middleware BEFORE the SuperTokens middleware as shown below. -::: - -```tsx -import express from "express"; -import cors from "cors"; -import supertokens from "supertokens-node"; -import {middleware} from "supertokens-node/framework/express"; - -let app = express(); - -app.use(cors({ - // highlight-start - origin: "^{form_websiteDomain}", - allowedHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], - credentials: true, - // highlight-end -})); - -// IMPORTANT: CORS should be before the below line. -// highlight-next-line -app.use(middleware()); - -// ...your API routes -``` -This `middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): -- `POST ^{form_apiBasePath}/signinup/code`: For starting the passwordless login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/resend`: To generate and resend a code during an already started login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/consume`: For finishing the passwordless login/sign up process -- `GET ^{form_apiBasePath}/passwordless/email/exists`: To check if an email is already signed up -- `GET ^{form_apiBasePath}/passwordless/phonenumber/exists`: To check if a phonenumber is already signed up -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. - - - - -Register the `plugin`. - -```tsx -import Hapi from "@hapi/hapi"; -import supertokens from "supertokens-node"; -import { plugin } from "supertokens-node/framework/hapi"; - -const server = Hapi.server({ - port: 8000, - routes: { - // highlight-start - cors: { - origin: ["^{form_websiteDomain}"], - additionalHeaders: [...supertokens.getAllCORSHeaders()], - credentials: true, - } - // highlight-end - } -}); - -(async() => { - // highlight-next-line - await server.register(plugin); - - await server.start(); -})(); - -// ...your API routes -``` -This `plugin` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)) as well take care of all the errors thrown by the Supertokens library: -- `POST ^{form_apiBasePath}/signinup/code`: For starting the passwordless login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/resend`: To generate and resend a code during an already started login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/consume`: For finishing the passwordless login/sign up process -- `GET ^{form_apiBasePath}/passwordless/email/exists`: To check if an email is already signed up -- `GET ^{form_apiBasePath}/passwordless/phonenumber/exists`: To check if a phonenumber is already signed up -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. - - - - - -Register the `plugin`. Also register [`@fastify/formbody`](https://github.com/fastify/fastify-formbody) plugin. - -```tsx -import cors from "@fastify/cors"; -import supertokens from "supertokens-node"; -import { plugin } from "supertokens-node/framework/fastify"; -import formDataPlugin from "@fastify/formbody"; - -import fastifyImport from "fastify"; -let fastify = fastifyImport(); - -// ...other middlewares -(async () => { - // highlight-start - await fastify.register(cors, { - origin: "^{form_websiteDomain}", - allowedHeaders: ['Content-Type', ...supertokens.getAllCORSHeaders()], - credentials: true, - }) - // highlight-end - // highlight-next-line - await fastify.register(formDataPlugin); - // highlight-next-line - await fastify.register(plugin); - - await fastify.listen(3000); -})(); - -// ...your API routes -``` -This `plugin` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): -- `POST ^{form_apiBasePath}/signinup/code`: For starting the passwordless login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/resend`: To generate and resend a code during an already started login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/consume`: For finishing the passwordless login/sign up process -- `GET ^{form_apiBasePath}/passwordless/email/exists`: To check if an email is already signed up -- `GET ^{form_apiBasePath}/passwordless/phonenumber/exists`: To check if a phonenumber is already signed up -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. - - - - - -:::important -Add the `middleware` BEFORE all your routes. -::: - -```tsx -import Koa from "koa"; -import cors from '@koa/cors'; -import supertokens from "supertokens-node"; -import { middleware } from "supertokens-node/framework/koa"; - -let app = new Koa(); - -// ...other middlewares -app.use(cors({ - // highlight-start - origin: "^{form_websiteDomain}", - allowHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], - credentials: true, - // highlight-end -})); - -// highlight-next-line -app.use(middleware()); - -// ...your API routes -``` -This `middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): -- `POST ^{form_apiBasePath}/signinup/code`: For starting the passwordless login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/resend`: To generate and resend a code during an already started login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/consume`: For finishing the passwordless login/sign up process -- `GET ^{form_apiBasePath}/passwordless/email/exists`: To check if an email is already signed up -- `GET ^{form_apiBasePath}/passwordless/phonenumber/exists`: To check if a phonenumber is already signed up -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. - - - - - -:::important -Add the `middleware` BEFORE all your routes. -::: - -```tsx -import {RestApplication} from '@loopback/rest'; -import supertokens from "supertokens-node"; -import {middleware} from "supertokens-node/framework/loopback"; - -let app = new RestApplication({ - rest: { - cors: { - // highlight-start - origin: "^{form_websiteDomain}", - allowedHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], - credentials: true - // highlight-end - } - } -}); - -// highlight-next-line -app.middleware(middleware); - -// ...your API routes -``` -This `middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): -- `POST ^{form_apiBasePath}/signinup/code`: For starting the passwordless login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/resend`: To generate and resend a code during an already started login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/consume`: For finishing the passwordless login/sign up process -- `GET ^{form_apiBasePath}/passwordless/email/exists`: To check if an email is already signed up -- `GET ^{form_apiBasePath}/passwordless/phonenumber/exists`: To check if a phonenumber is already signed up -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. - - - - - - - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "net/http" - "strings" - - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - //highlight-start - http.ListenAndServe("SERVER ADDRESS", corsMiddleware( - supertokens.Middleware(http.HandlerFunc(func(rw http.ResponseWriter, - //highlight-end - r *http.Request) { - // TODO: Handle your APIs.. - - })))) -} - -func corsMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(response http.ResponseWriter, r *http.Request) { - response.Header().Set("Access-Control-Allow-Origin", "^{form_websiteDomain}") - response.Header().Set("Access-Control-Allow-Credentials", "true") - if r.Method == "OPTIONS" { - // we add content-type + other headers used by SuperTokens - response.Header().Set("Access-Control-Allow-Headers", - strings.Join(append([]string{"Content-Type"}, - //highlight-start - supertokens.GetAllCORSHeaders()...), ",")) - //highlight-end - response.Header().Set("Access-Control-Allow-Methods", "*") - response.Write([]byte("")) - } else { - next.ServeHTTP(response, r) - } - }) -} -``` - - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "net/http" - - "github.com/gin-contrib/cors" - "github.com/gin-gonic/gin" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - router := gin.New() - - // CORS - router.Use(cors.New(cors.Config{ - AllowOrigins: []string{"^{form_websiteDomain}"}, - AllowMethods: []string{"GET", "POST", "DELETE", "PUT", "OPTIONS"}, - AllowHeaders: append([]string{"content-type"}, - // highlight-next-line - supertokens.GetAllCORSHeaders()...), - AllowCredentials: true, - })) - - // Adding the SuperTokens middleware - // highlight-start - router.Use(func(c *gin.Context) { - supertokens.Middleware(http.HandlerFunc( - func(rw http.ResponseWriter, r *http.Request) { - c.Next() - })).ServeHTTP(c.Writer, c.Request) - // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly - c.Abort() - }) - // highlight-end - - // Add APIs and start server -} -``` - - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "github.com/go-chi/chi" - "github.com/go-chi/cors" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - r := chi.NewRouter() - - // CORS - r.Use(cors.Handler(cors.Options{ - AllowedOrigins: []string{"^{form_websiteDomain}"}, - AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, - AllowedHeaders: append([]string{"Content-Type"}, - //highlight-next-line - supertokens.GetAllCORSHeaders()...), - AllowCredentials: true, - })) - - // SuperTokens Middleware - //highlight-next-line - r.Use(supertokens.Middleware) - - // Add APIs and start server -} -``` - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "net/http" - - "github.com/gorilla/handlers" - "github.com/gorilla/mux" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - // Add APIs - - router := mux.NewRouter() - - // Adding handlers.CORS(options)(supertokens.Middleware(router))) - //highlight-start - http.ListenAndServe("SERVER ADDRESS", handlers.CORS( - handlers.AllowedHeaders(append([]string{"Content-Type"}, - supertokens.GetAllCORSHeaders()...)), - handlers.AllowedMethods([]string{"GET", "POST", "PUT", "HEAD", "OPTIONS"}), - handlers.AllowedOrigins([]string{"^{form_websiteDomain}"}), - handlers.AllowCredentials(), - )(supertokens.Middleware(router))) - //highlight-end -} -``` - - - -This `Middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): -- `POST ^{form_apiBasePath}/signinup/code`: For starting the passwordless login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/resend`: To generate and resend a code during an already started login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/consume`: For finishing the passwordless login/sign up process -- `GET ^{form_apiBasePath}/passwordless/email/exists`: To check if an email is already signed up -- `GET ^{form_apiBasePath}/passwordless/phonenumber/exists`: To check if a phonenumber is already signed up -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. - - - - - - - - -Use the `Middleware` (**BEFORE all your routes**) and the `get_all_cors_headers()` functions as shown below. - -```python -from supertokens_python import get_all_cors_headers -from fastapi import FastAPI -from starlette.middleware.cors import CORSMiddleware -from supertokens_python.framework.fastapi import get_middleware - -app = FastAPI() -# highlight-next-line -app.add_middleware(get_middleware()) - -# TODO: Add APIs - -app.add_middleware( - CORSMiddleware, - allow_origins=[ - "^{form_websiteDomain}" - ], - allow_credentials=True, - allow_methods=["GET", "PUT", "POST", "DELETE", "OPTIONS", "PATCH"], - # highlight-next-line - allow_headers=["Content-Type"] + get_all_cors_headers(), -) - -# TODO: start server -``` - - - - -- Use the `Middleware` (**BEFORE all your routes and after calling init function**) and the `get_all_cors_headers()` functions as shown below. -- Add a route to catch all paths and return a 404. This is needed because if we don't add this, then OPTIONS request for the APIs exposed by the `Middleware` will return a `404`. - -```python -from supertokens_python import get_all_cors_headers -from flask import Flask, abort -from flask_cors import CORS # type: ignore -from supertokens_python.framework.flask import Middleware - -app = Flask(__name__) -# highlight-next-line -Middleware(app) - -# TODO: Add APIs - -CORS( - app=app, - origins=[ - "^{form_websiteDomain}" - ], - supports_credentials=True, - # highlight-next-line - allow_headers=["Content-Type"] + get_all_cors_headers(), -) - -# This is required since if this is not there, then OPTIONS requests for -# the APIs exposed by the supertokens' Middleware will return a 404 -# highlight-start -@app.route('/', defaults={'u_path': ''}) # type: ignore -@app.route('/') # type: ignore -def catch_all(u_path: str): - abort(404) -# highlight-end - -# TODO: start server -``` - - - - -Use the `Middleware` and the `get_all_cors_headers()` functions as shown below in your `settings.py`. - -```python -from supertokens_python import get_all_cors_headers -from typing import List -from corsheaders.defaults import default_headers - -CORS_ORIGIN_WHITELIST = [ - "^{form_websiteDomain}" -] - -CORS_ALLOW_CREDENTIALS = True - -CORS_ALLOWED_ORIGINS = [ - "^{form_websiteDomain}" -] - -CORS_ALLOW_HEADERS: List[str] = list(default_headers) + [ - "Content-Type" - # highlight-next-line -] + get_all_cors_headers() - -INSTALLED_APPS = [ - 'corsheaders', - 'supertokens_python' -] - -MIDDLEWARE = [ - 'corsheaders.middleware.CorsMiddleware', - ..., - # highlight-next-line - 'supertokens_python.framework.django.django_middleware.middleware', -] -``` - - - - -This `Middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): -- `POST ^{form_apiBasePath}/signinup/code`: For starting the passwordless login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/resend`: To generate and resend a code during an already started login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/consume`: For finishing the passwordless login/sign up process -- `GET ^{form_apiBasePath}/passwordless/email/exists`: To check if an email is already signed up -- `GET ^{form_apiBasePath}/passwordless/phonenumber/exists`: To check if a phonenumber is already signed up -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. - - - - - - - -## 5) Add the SuperTokens error handler -Add the `errorHandler` **AFTER all your routes**, but **BEFORE your error handler** - - - - - - -```tsx -import express from "express"; -import {errorHandler} from "supertokens-node/framework/express"; - -const app = express(); -// ...your API routes - -// highlight-start -// Add this AFTER all your routes -app.use(errorHandler()) -// highlight-end - -// your own error handler -app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => { - // TODO -}); - -``` - - - -No additional `errorHandler` is required. - - - - -Add the `errorHandler` **Before all your routes and plugin registration** - -```tsx -import Fastify from "fastify"; -import {errorHandler} from "supertokens-node/framework/fastify"; - -const fastify = Fastify(); - -// highlight-next-line -fastify.setErrorHandler(errorHandler()); - -// ...your API routes - -``` - - - -No additional `errorHandler` is required. - - - - -No additional `errorHandler` is required. - - - - - - -:::info -You can skip this step -::: - - - - -:::info -You can skip this step -::: - - - - -## 6) Setup the SuperTokens core - -You need to now setup an instance of the SuperTokens core for your app (that your backend should connect to). You have two options: -- [Managed service](./core/saas-setup) -- Self hosted with your own database ([With Docker](./core/with-docker) or [Without Docker](./core/without-docker)) diff --git a/v2/thirdpartypasswordless/custom-ui/init/core/managed-service.mdx b/v2/thirdpartypasswordless/custom-ui/init/core/managed-service.mdx index 5dade6aca..726e4dc4a 100644 --- a/v2/thirdpartypasswordless/custom-ui/init/core/managed-service.mdx +++ b/v2/thirdpartypasswordless/custom-ui/init/core/managed-service.mdx @@ -4,95 +4,9 @@ title: Managed Service hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import TabItem from '@theme/TabItem'; -# Managed Service + -## Creating a development environment ✨ -- First, please [sign up](https://supertokens.com/auth) -- Select the auth method you want to use and follow the guided steps to integrate with our frontend and backend SDK if you have not done this already -Integration with SuperTokens SDKs -- Select a region and click the deploy button: -:::tip -You should select a region that is closest to your backend. -::: - -Deploying SuperTokens Core - -- After the deployment is complete the dashboard will look similar to this: -Deployed SuperTokens Core - -## Connecting the backend SDK with SuperTokens 🔌 -- Add the `connectionURI` and the `apiKey` shown on the dashboard to your code on the backend. - -SuperTokens managed service dashboard connectionURI and API key - - - - -```tsx -import supertokens from "supertokens-node"; - -supertokens.init({ - // highlight-start - supertokens: { - connectionURI: "", - apiKey: "" - }, - // highlight-end - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "..." - }, - recipeList: [] -}); -``` - - - - -```go -import "github.com/supertokens/supertokens-golang/supertokens" - -func main() { - supertokens.Init(supertokens.TypeInput{ - // highlight-start - Supertokens: &supertokens.ConnectionInfo{ - ConnectionURI: "", - APIKey: "", - }, - // highlight-end - }) -} - -``` - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig - -init( - app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), - # highlight-start - supertokens_config=SupertokensConfig( - connection_uri='', - api_key='' - ), - # highlight-end - framework='...', # type: ignore - recipe_list=[ - #... - ] -) -``` - - - diff --git a/v2/thirdpartypasswordless/custom-ui/init/core/self-hosted-with-docker.mdx b/v2/thirdpartypasswordless/custom-ui/init/core/self-hosted-with-docker.mdx index 1df643ac0..e31d7f6e6 100644 --- a/v2/thirdpartypasswordless/custom-ui/init/core/self-hosted-with-docker.mdx +++ b/v2/thirdpartypasswordless/custom-ui/init/core/self-hosted-with-docker.mdx @@ -4,268 +4,8 @@ title: With Docker hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import DatabaseTabs from "/src/components/tabs/DatabaseTabs" -import TabItem from '@theme/TabItem'; -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import DockerVersionProvider from "/src/components/dockerVersionProvider"; -# With Docker + -## Running the docker image 🚀 - - - - - - -```bash -docker run -p 3567:3567 -d registry.supertokens.io/supertokens/supertokens-mysql^{docker_version_mysql} -``` - -- To see all the env variables available, please see [the README file](https://github.com/supertokens/supertokens-docker-mysql/blob/master/README.md). -- The above command will start the container with an in-memory database. This means you **do not need to connect it to MySQL to test out SuperTokens**. -- When you are ready to connect it to your database, please visit the [Database setup section](../database-setup/mysql) - - - - - -```bash -docker run -p 3567:3567 -d registry.supertokens.io/supertokens/supertokens-postgresql^{docker_version_postgresql} -``` - -- To see all the env variables available, please see [the README file](https://github.com/supertokens/supertokens-docker-postgresql/blob/master/README.md). -- The above command will start the container with an in-memory database. This means you **do not need to connect it to PostgreSQL to test out SuperTokens**. -- When you are ready to connect it to your database, please visit the [Database setup section](../database-setup/postgresql) - - - - - -```bash -docker run -p 3567:3567 -d registry.supertokens.io/supertokens/supertokens-mongodb^{docker_version_mongodb} -``` - -- To see all the env variables available, please see [the README file](https://github.com/supertokens/supertokens-docker-mongodb/blob/master/README.md). -- The above command will start the container with an in-memory database. This means you **do not need to connect it to MongoDB to test out SuperTokens**. -- When you are ready to connect it to your database, please visit the [Database setup section](../database-setup/mongodb) - -:::caution -We do not offer login functionality with MongDB yet. We only offer session management. -::: - - - - - -## Testing that the service is running 🤞 -Open a browser and visit `http://localhost:3567/hello`. If you see a page that says `Hello` back, then the container was started successfully! - -If you are having issues with starting the docker image, please feel free to reach out to us [over email](mailto:team@supertokens.com) or [via Discord](https://supertokens.com/discord). - -:::tip -The `/hello` route checks whether the database connection is set up correctly and will only return a 200 status code if there is no issue. - -If you are using kubernetes or docker swarm, this endpoint is perfect for doing readiness and liveness probes. -::: - -## Connecting the backend SDK with SuperTokens 🔌 -- The default `port` for SuperTokens is `3567`. You can change this by binding a different port in the `docker run` command. For example, `docker run -p 8080:3567` will run SuperTokens on port `8080` on your machine. -- The connection info will go in the `supertokens` object in the `init` function on your backend: - - - - -```tsx -import supertokens from "supertokens-node"; - -supertokens.init({ - // highlight-start - supertokens: { - connectionURI: "http://localhost:3567", - apiKey: "someKey" // OR can be undefined - }, - // highlight-end - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "..." - }, - recipeList: [] -}); -``` - - - - -```go -import "github.com/supertokens/supertokens-golang/supertokens" - -func main() { - supertokens.Init(supertokens.TypeInput{ - // highlight-start - Supertokens: &supertokens.ConnectionInfo{ - ConnectionURI: "http://localhost:3567", - APIKey: "someKey", - }, - // highlight-end - }) -} - -``` - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig - -init( - app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), - # highlight-start - supertokens_config=SupertokensConfig( - connection_uri='http://localhost:3567', - api_key='someKey' - ), - # highlight-end - framework='...', # type: ignore - recipe_list=[ - #... - ] -) -``` - - - - -:::tip Security -There is no API key by default. Visit the "Auth flow customization" -> "SuperTokens core settings" -> "Adding API Keys" section to see how to add one. -::: - -## Docker compose file - - - - - - -```bash -version: '3' - -services: - db: - image: mysql:latest - environment: - MYSQL_ROOT_PASSWORD: root - MYSQL_USER: supertokens_user - MYSQL_PASSWORD: somePassword - MYSQL_DATABASE: supertokens - ports: - - 3306:3306 - networks: - - app_network - restart: unless-stopped - healthcheck: - test: [ "CMD", "mysqladmin", "ping", "-h", "localhost" ] - timeout: 20s - retries: 10 - - supertokens: - image: registry.supertokens.io/supertokens/supertokens-mysql^{docker_version_mysql} - depends_on: - db: - condition: service_healthy - ports: - - 3567:3567 - environment: - MYSQL_CONNECTION_URI: mysql://supertokens_user:somePassword@db:3306/supertokens - networks: - - app_network - restart: unless-stopped - healthcheck: - test: > - bash -c 'exec 3<>/dev/tcp/127.0.0.1/3567 && echo -e "GET /hello HTTP/1.1\r\nhost: 127.0.0.1:3567\r\nConnection: close\r\n\r\n" >&3 && cat <&3 | grep "Hello"' - interval: 10s - timeout: 5s - retries: 5 - -networks: - app_network: - driver: bridge -``` - - - - - -```bash -version: '3' - -services: - # Note: If you are assigning a custom name to your db service on the line below, make sure it does not contain underscores - db: - image: 'postgres:latest' - environment: - POSTGRES_USER: supertokens_user - POSTGRES_PASSWORD: somePassword - POSTGRES_DB: supertokens - ports: - - 5432:5432 - networks: - - app_network - restart: unless-stopped - healthcheck: - test: ['CMD', 'pg_isready', '-U', 'supertokens_user', '-d', 'supertokens'] - interval: 5s - timeout: 5s - retries: 5 - - supertokens: - image: registry.supertokens.io/supertokens/supertokens-postgresql^{docker_version_postgresql} - depends_on: - db: - condition: service_healthy - ports: - - 3567:3567 - environment: - POSTGRESQL_CONNECTION_URI: "postgresql://supertokens_user:somePassword@db:5432/supertokens" - networks: - - app_network - restart: unless-stopped - healthcheck: - test: > - bash -c 'exec 3<>/dev/tcp/127.0.0.1/3567 && echo -e "GET /hello HTTP/1.1\r\nhost: 127.0.0.1:3567\r\nConnection: close\r\n\r\n" >&3 && cat <&3 | grep "Hello"' - interval: 10s - timeout: 5s - retries: 5 - -networks: - app_network: - driver: bridge -``` - - - - - - -We are working on adding this section. - - - - - - -:::important -If you are running the backend process that integrates with our backend sdk as part of the docker compose file as well, make sure to use `http://supertokens:3567` as the connection uri instead of `http://localhost:3567`. -::: - - -## Helm charts for Kubernetes - -- For [MySQL image](https://github.com/supertokens/supertokens-docker-mysql/tree/master/helm-chart) - -- For [PostgreSQL image](https://github.com/supertokens/supertokens-docker-postgresql/tree/master/helm-chart) diff --git a/v2/thirdpartypasswordless/custom-ui/init/core/self-hosted-without-docker.mdx b/v2/thirdpartypasswordless/custom-ui/init/core/self-hosted-without-docker.mdx index 9a44a4e8c..7068a55cb 100644 --- a/v2/thirdpartypasswordless/custom-ui/init/core/self-hosted-without-docker.mdx +++ b/v2/thirdpartypasswordless/custom-ui/init/core/self-hosted-without-docker.mdx @@ -4,165 +4,9 @@ title: Without Docker hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import OSTabs from "/src/components/tabs/OSTabs"; -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -# Binary Installation + -## 1) Download SuperTokens -- Visit the [open source download page](https://supertokens.com/use-oss). -- Click on the "Binary" tab. -- Choose your database. -- Download the SuperTokens zip file for your OS. - -Once downloaded, extract the zip, and you will see a folder named `supertokens`. - -## 2) Install SuperTokens - - - - -```bash -# sudo is required so that the supertokens -# command can be added to your PATH variable. - -cd supertokens -sudo ./install -``` - - - - -```bash - -cd supertokens -./install - -``` -:::caution -You may get an error like `java cannot be opened because the developer cannot be verified`. To solve this, visit System Preferences > Security & Privacy > General Tab, and then click on the Allow button at the bottom. Then retry the command above. -::: - - - - - -```batch - -Rem run as an Administrator. This is required so that the supertokens -Rem command can be added to your PATH. - -cd supertokens -install.bat - -``` - - - -:::important -After installing, you can delete the downloaded folder as you no longer need it. - -Any changes to the the config will be done in the `config.yaml` file in the installation directory, the location of which is specified in the output of the `supertokens --help` command. -::: - -## 3) Start SuperTokens 🚀 -Running the following command will start the service. -```bash -supertokens start [--host=...] [--port=...] -``` -- The above command will start the container with an in-memory database. -- When you are ready to connect it to your database, please visit the [Database setup section](../database-setup/mysql) -- To see all available options please run `supertokens start --help` - - -## 4) Testing that the service is running 🤞 -Open a browser and visit `http://localhost:3567/hello`. If you see a page that says `Hello` back, then SuperTokens was started successfully! - -If you are having issues with starting the docker image, please feel free to reach out to us [over email](mailto:team@supertokens.com) or [via Discord](https://supertokens.com/discord). - - -## 5) Stopping SuperTokens 🛑 -```bash -supertokens stop -``` - -## Connecting the backend SDK with SuperTokens 🔌 -- The default `host` and `port` for SuperTokens is `localhost:3567`. You can change this by passing `--host` and `--port` options to the `start` command. -- The connection info will go in the `supertokens` object in the `init` function on your backend: - - - - - -```tsx -import supertokens from "supertokens-node"; - -supertokens.init({ - // highlight-start - supertokens: { - connectionURI: "http://localhost:3567", - apiKey: "someKey" // OR can be undefined - }, - // highlight-end - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "..." - }, - recipeList: [] -}); - -``` - - - - -```go -import "github.com/supertokens/supertokens-golang/supertokens" - -func main() { - supertokens.Init(supertokens.TypeInput{ - // highlight-start - Supertokens: &supertokens.ConnectionInfo{ - ConnectionURI: "http://localhost:3567", - APIKey: "someKey", - }, - // highlight-end - }) -} - -``` - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig - -init( - app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), - # highlight-start - supertokens_config=SupertokensConfig( - connection_uri='http://localhost:3567', - api_key='someKey' - ), - # highlight-end - framework='...', # type: ignore - recipe_list=[ - #... - ] -) -``` - - - - -:::tip Security -There is no API key by default. Visit the "Auth flow customization" -> "SuperTokens core settings" -> "Adding API Keys" section to see how to add one. -::: diff --git a/v2/thirdpartypasswordless/custom-ui/init/database-setup/mysql.mdx b/v2/thirdpartypasswordless/custom-ui/init/database-setup/mysql.mdx index eb37456b2..3597e3e64 100644 --- a/v2/thirdpartypasswordless/custom-ui/init/database-setup/mysql.mdx +++ b/v2/thirdpartypasswordless/custom-ui/init/database-setup/mysql.mdx @@ -4,533 +4,7 @@ title: If using MySQL hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import SelfHostedTabs from "/src/components/tabs/SelfHostedTabs" -import TabItem from '@theme/TabItem'; + -# MySQL setup - -:::important -This is needed only if you are running the SuperTokens core yourself. - -The minimum required version is **MySQL 5.7**. -::: - -:::tip blog -We also have [a blog post writeup](https://supertokens.com/blog/connect-supertokens-to-database) highlighting all the steps in more detail for different scenarios. -::: - -## 1) Create a database 🛠️ -```sql -CREATE DATABASE supertokens; -``` -You can skip this step if you want SuperTokens to write to your own database. In this case, you will need to provide your database's name as shown in the step below. - -## 2) Connect SuperTokens to your database 🔌 - - - - -:::caution -Host being `localhost` / `127.0.0.1` will not work in a docker image. Instead, please provide the database's local / public hostname or IP address. - -You also need to make the database listen on all the IP's of the local machine. -This can be done by editing the `mysqld.cnf` config file and setting the value of `bind-address` to `0.0.0.0`. -::: - -```bash - -docker run \ - -p 3567:3567 \ -//highlight-next-line - -e MYSQL_CONNECTION_URI="mysql://username:pass@host/dbName" \ - -d registry.supertokens.io/supertokens/supertokens-mysql - -# OR - -docker run \ - -p 3567:3567 \ -//highlight-start - -e MYSQL_USER="username" \ - -e MYSQL_PASSWORD="password" \ - -e MYSQL_HOST="host" \ - -e MYSQL_PORT="3306" \ - -e MYSQL_DATABASE_NAME="supertokens" \ -//highlight-end - -d registry.supertokens.io/supertokens/supertokens-mysql -``` - - - - - - - -```yaml -# You need to add the following to the config.yaml file. -# The file path can be found by running the "supertokens --help" command - -mysql_connection_uri: "mysql://username:pass@host/dbName" - -# OR - -mysql_user: "username" - -mysql_password: "password" - -mysql_host: "host" - -mysql_port: 3306 - -mysql_database_name: "supertokens" -``` - - - - -## 3) Create tables 👩‍💻👨‍💻 - -:::note -This happens automatically, unless you provide a MySQL user that doesn't have table creation permission. -::: - -```sql -CREATE TABLE `apps` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `created_at_time` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`) -); - -CREATE TABLE `tenants` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `created_at_time` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `tenant_configs` ( - `connection_uri_domain` varchar(256) NOT NULL DEFAULT '', - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `core_config` text, - `email_password_enabled` tinyint(1) DEFAULT NULL, - `passwordless_enabled` tinyint(1) DEFAULT NULL, - `third_party_enabled` tinyint(1) DEFAULT NULL, - `is_first_factors_null` tinyint(1) DEFAULT NULL, - PRIMARY KEY (`connection_uri_domain`,`app_id`,`tenant_id`) -); - -CREATE TABLE `tenant_thirdparty_providers` ( - `connection_uri_domain` varchar(256) NOT NULL DEFAULT '', - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `third_party_id` varchar(28) NOT NULL, - `name` varchar(64) DEFAULT NULL, - `authorization_endpoint` text, - `authorization_endpoint_query_params` text, - `token_endpoint` text, - `token_endpoint_body_params` text, - `user_info_endpoint` text, - `user_info_endpoint_query_params` text, - `user_info_endpoint_headers` text, - `jwks_uri` text, - `oidc_discovery_endpoint` text, - `require_email` tinyint(1) DEFAULT NULL, - `user_info_map_from_id_token_payload_user_id` varchar(64) DEFAULT NULL, - `user_info_map_from_id_token_payload_email` varchar(64) DEFAULT NULL, - `user_info_map_from_id_token_payload_email_verified` varchar(64) DEFAULT NULL, - `user_info_map_from_user_info_endpoint_user_id` varchar(64) DEFAULT NULL, - `user_info_map_from_user_info_endpoint_email` varchar(64) DEFAULT NULL, - `user_info_map_from_user_info_endpoint_email_verified` varchar(64) DEFAULT NULL, - PRIMARY KEY (`connection_uri_domain`,`app_id`,`tenant_id`,`third_party_id`), - FOREIGN KEY (`connection_uri_domain`, `app_id`, `tenant_id`) REFERENCES `tenant_configs` (`connection_uri_domain`, `app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE TABLE `tenant_thirdparty_provider_clients` ( - `connection_uri_domain` varchar(256) NOT NULL DEFAULT '', - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `third_party_id` varchar(28) NOT NULL, - `client_type` varchar(64) NOT NULL DEFAULT '', - `client_id` varchar(256) NOT NULL, - `client_secret` text, - `scope` text, - `force_pkce` tinyint(1) DEFAULT NULL, - `additional_config` text, - PRIMARY KEY (`connection_uri_domain`,`app_id`,`tenant_id`,`third_party_id`,`client_type`), - FOREIGN KEY (`connection_uri_domain`, `app_id`, `tenant_id`, `third_party_id`) REFERENCES `tenant_thirdparty_providers` (`connection_uri_domain`, `app_id`, `tenant_id`, `third_party_id`) ON DELETE CASCADE -); - -CREATE TABLE `tenant_first_factors` ( - connection_uri_domain VARCHAR(256) DEFAULT '', - app_id VARCHAR(64) DEFAULT 'public', - tenant_id VARCHAR(64) DEFAULT 'public', - factor_id VARCHAR(128), - PRIMARY KEY (`connection_uri_domain`, `app_id`, `tenant_id`, `factor_id`), - FOREIGN KEY (`connection_uri_domain`, `app_id`, `tenant_id`) REFERENCES `tenant_configs` (`connection_uri_domain`, `app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE TABLE `tenant_required_secondary_factors` ( - connection_uri_domain VARCHAR(256) DEFAULT '', - app_id VARCHAR(64) DEFAULT 'public', - tenant_id VARCHAR(64) DEFAULT 'public', - factor_id VARCHAR(128), - PRIMARY KEY (`connection_uri_domain`, `app_id`, `tenant_id`, `factor_id`), - FOREIGN KEY (`connection_uri_domain`, `app_id`, `tenant_id`) REFERENCES `tenant_configs` (`connection_uri_domain`, `app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE TABLE `key_value` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `name` varchar(128) NOT NULL, - `value` text, - `created_at_time` bigint unsigned DEFAULT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`name`), - FOREIGN KEY (`app_id`, `tenant_id`) REFERENCES `tenants` (`app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE TABLE `app_id_to_user_id` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `recipe_id` varchar(128) NOT NULL, - `primary_or_recipe_user_id` char(36) NOT NULL, - `is_linked_or_is_a_primary_user` BOOLEAN NOT NULL DEFAULT FALSE, - PRIMARY KEY (`app_id`,`user_id`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE, - FOREIGN KEY (`app_id`, `primary_or_recipe_user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE INDEX app_id_to_user_id_primary_user_id_index ON `app_id_to_user_id` (`primary_or_recipe_user_id`); - -CREATE INDEX app_id_to_user_id_user_id_index ON `app_id_to_user_id` (`user_id`); - -CREATE TABLE `all_auth_recipe_users` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `primary_or_recipe_user_id` char(36) NOT NULL, - `is_linked_or_is_a_primary_user` BOOLEAN NOT NULL DEFAULT FALSE, - `recipe_id` varchar(128) NOT NULL, - `time_joined` bigint unsigned NOT NULL, - `primary_or_recipe_user_time_joined` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`user_id`), - KEY `app_id` (`app_id`,`user_id`), - FOREIGN KEY (`app_id`, `tenant_id`) REFERENCES `tenants` (`app_id`, `tenant_id`) ON DELETE CASCADE, - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE, - FOREIGN KEY (`app_id`, `primary_or_recipe_user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE INDEX all_auth_recipe_users_pagination_index1 ON all_auth_recipe_users - (app_id, tenant_id, primary_or_recipe_user_time_joined DESC, primary_or_recipe_user_id DESC); - -CREATE INDEX all_auth_recipe_users_pagination_index3 ON all_auth_recipe_users - (recipe_id, app_id, tenant_id, primary_or_recipe_user_time_joined DESC, primary_or_recipe_user_id DESC); - -CREATE INDEX all_auth_recipe_users_primary_user_id_index ON all_auth_recipe_users - (primary_or_recipe_user_id, app_id); - -CREATE INDEX all_auth_recipe_users_recipe_id_index ON all_auth_recipe_users - (app_id, recipe_id, tenant_id); - -CREATE TABLE `userid_mapping` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `supertokens_user_id` char(36) NOT NULL, - `external_user_id` varchar(128) NOT NULL, - `external_user_id_info` text, - PRIMARY KEY (`app_id`,`supertokens_user_id`,`external_user_id`), - UNIQUE KEY `supertokens_user_id` (`app_id`,`supertokens_user_id`), - UNIQUE KEY `external_user_id` (`app_id`,`external_user_id`), - FOREIGN KEY (`app_id`, `supertokens_user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE TABLE `dashboard_users` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `email` varchar(256) NOT NULL, - `password_hash` varchar(256) NOT NULL, - `time_joined` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`user_id`), - UNIQUE KEY `email` (`app_id`,`email`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `dashboard_user_sessions` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `session_id` char(36) NOT NULL, - `user_id` char(36) NOT NULL, - `time_created` bigint unsigned NOT NULL, - `expiry` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`session_id`), - KEY `app_id` (`app_id`,`user_id`), - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `dashboard_users` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE INDEX `dashboard_user_sessions_expiry_index` ON `dashboard_user_sessions` (`expiry`); - -CREATE TABLE `session_access_token_signing_keys` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `created_at_time` bigint unsigned NOT NULL, - `value` text, - PRIMARY KEY (`app_id`,`created_at_time`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `session_info` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `session_handle` varchar(255) NOT NULL, - `user_id` varchar(128) NOT NULL, - `refresh_token_hash_2` varchar(128) NOT NULL, - `session_data` text, - `expires_at` bigint unsigned NOT NULL, - `created_at_time` bigint unsigned NOT NULL, - `jwt_user_payload` text, - `use_static_key` tinyint(1) NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`session_handle`), - FOREIGN KEY (`app_id`, `tenant_id`) REFERENCES `tenants` (`app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE INDEX `session_expiry_index` ON `session_info` (`expires_at`); - -CREATE TABLE `user_last_active` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - `last_active_time` bigint unsigned DEFAULT NULL, - PRIMARY KEY (`app_id`,`user_id`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `emailpassword_users` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `email` varchar(256) NOT NULL, - `password_hash` varchar(256) NOT NULL, - `time_joined` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`user_id`), - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE TABLE `emailpassword_user_to_tenant` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `email` varchar(256) NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`user_id`), - UNIQUE KEY `email` (`app_id`,`tenant_id`,`email`), - FOREIGN KEY (`app_id`, `tenant_id`, `user_id`) REFERENCES `all_auth_recipe_users` (`app_id`, `tenant_id`, `user_id`) ON DELETE CASCADE -); - -CREATE TABLE `emailpassword_pswd_reset_tokens` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `token` varchar(128) NOT NULL, - `token_expiry` bigint unsigned NOT NULL, - `email` varchar(256), - PRIMARY KEY (`app_id`,`user_id`,`token`), - UNIQUE KEY `token` (`token`), - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE ON UPDATE CASCADE -); - -CREATE INDEX `emailpassword_password_reset_token_expiry_index` ON `emailpassword_pswd_reset_tokens` (`token_expiry`); - -CREATE TABLE `emailverification_verified_emails` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - `email` varchar(256) NOT NULL, - PRIMARY KEY (`app_id`,`user_id`,`email`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `emailverification_tokens` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - `email` varchar(256) NOT NULL, - `token` varchar(128) NOT NULL, - `token_expiry` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`user_id`,`email`,`token`), - UNIQUE KEY `token` (`token`), - FOREIGN KEY (`app_id`, `tenant_id`) REFERENCES `tenants` (`app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE INDEX `emailverification_tokens_index` ON `emailverification_tokens` (`token_expiry`); - -CREATE TABLE `thirdparty_users` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `third_party_id` varchar(28) NOT NULL, - `third_party_user_id` varchar(256) NOT NULL, - `user_id` char(36) NOT NULL, - `email` varchar(256) NOT NULL, - `time_joined` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`user_id`), - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE INDEX `thirdparty_users_email_index` ON `thirdparty_users` (`app_id`,`email`); - -CREATE INDEX `thirdparty_users_thirdparty_user_id_index` ON `thirdparty_users` (`app_id`,`third_party_id`,`third_party_user_id`); - -CREATE TABLE `thirdparty_user_to_tenant` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `third_party_id` varchar(28) NOT NULL, - `third_party_user_id` varchar(256) NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`user_id`), - UNIQUE KEY `third_party_user_id` (`app_id`,`tenant_id`,`third_party_id`,`third_party_user_id`), - FOREIGN KEY (`app_id`, `tenant_id`, `user_id`) REFERENCES `all_auth_recipe_users` (`app_id`, `tenant_id`, `user_id`) ON DELETE CASCADE -); - -CREATE TABLE `passwordless_users` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `email` varchar(256) DEFAULT NULL, - `phone_number` varchar(256) DEFAULT NULL, - `time_joined` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`user_id`), - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `app_id_to_user_id` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE TABLE `passwordless_user_to_tenant` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` char(36) NOT NULL, - `email` varchar(256) DEFAULT NULL, - `phone_number` varchar(256) DEFAULT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`user_id`), - UNIQUE KEY `email` (`app_id`,`tenant_id`,`email`), - UNIQUE KEY `phone_number` (`app_id`,`tenant_id`,`phone_number`), - FOREIGN KEY (`app_id`, `tenant_id`, `user_id`) REFERENCES `all_auth_recipe_users` (`app_id`, `tenant_id`, `user_id`) ON DELETE CASCADE -); - -CREATE TABLE `passwordless_devices` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `device_id_hash` char(44) NOT NULL, - `email` varchar(256) DEFAULT NULL, - `phone_number` varchar(256) DEFAULT NULL, - `link_code_salt` char(44) NOT NULL, - `failed_attempts` int unsigned NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`device_id_hash`), - FOREIGN KEY (`app_id`, `tenant_id`) REFERENCES `tenants` (`app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE INDEX `passwordless_devices_email_index` ON `passwordless_devices` (`app_id`,`tenant_id`,`email`); - -CREATE INDEX `passwordless_devices_phone_number_index` ON `passwordless_devices` (`app_id`,`tenant_id`,`phone_number`); - -CREATE TABLE `passwordless_codes` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `code_id` char(36) NOT NULL, - `device_id_hash` char(44) NOT NULL, - `link_code_hash` char(44) NOT NULL, - `created_at` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`code_id`), - UNIQUE KEY `link_code_hash` (`app_id`,`tenant_id`,`link_code_hash`), - KEY `app_id` (`app_id`,`tenant_id`,`device_id_hash`), - FOREIGN KEY (`app_id`, `tenant_id`, `device_id_hash`) REFERENCES `passwordless_devices` (`app_id`, `tenant_id`, `device_id_hash`) ON DELETE CASCADE ON UPDATE CASCADE -); - -CREATE INDEX `passwordless_codes_created_at_index` ON `passwordless_codes` (`app_id`,`tenant_id`,`created_at`); - -CREATE TABLE `jwt_signing_keys` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `key_id` varchar(255) NOT NULL, - `key_string` text NOT NULL, - `algorithm` varchar(10) NOT NULL, - `created_at` bigint unsigned DEFAULT NULL, - PRIMARY KEY (`app_id`,`key_id`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `user_metadata` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - `user_metadata` text NOT NULL, - PRIMARY KEY (`app_id`,`user_id`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `roles` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `role` varchar(255) NOT NULL, - PRIMARY KEY (`app_id`,`role`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `role_permissions` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `role` varchar(255) NOT NULL, - `permission` varchar(255) NOT NULL, - PRIMARY KEY (`app_id`,`role`,`permission`), - FOREIGN KEY (`app_id`, `role`) REFERENCES `roles` (`app_id`, `role`) ON DELETE CASCADE -); - -CREATE INDEX `role_permissions_permission_index` ON `role_permissions` (`app_id`,`permission`); - -CREATE TABLE `user_roles` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - `role` varchar(255) NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`user_id`,`role`), - FOREIGN KEY (`app_id`, `tenant_id`) REFERENCES `tenants` (`app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE INDEX `user_roles_role_index` ON `user_roles` (`app_id`,`tenant_id`,`role`); - -CREATE TABLE `totp_users` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - PRIMARY KEY (`app_id`,`user_id`), - FOREIGN KEY (`app_id`) REFERENCES `apps` (`app_id`) ON DELETE CASCADE -); - -CREATE TABLE `totp_user_devices` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - `device_name` varchar(256) NOT NULL, - `secret_key` varchar(256) NOT NULL, - `period` int NOT NULL, - `skew` int NOT NULL, - `verified` tinyint(1) NOT NULL, - `created_at` BIGINT UNSIGNED, - PRIMARY KEY (`app_id`,`user_id`,`device_name`), - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `totp_users` (`app_id`, `user_id`) ON DELETE CASCADE -); - -CREATE TABLE `totp_used_codes` ( - `app_id` varchar(64) NOT NULL DEFAULT 'public', - `tenant_id` varchar(64) NOT NULL DEFAULT 'public', - `user_id` varchar(128) NOT NULL, - `code` varchar(8) NOT NULL, - `is_valid` tinyint(1) NOT NULL, - `expiry_time_ms` bigint unsigned NOT NULL, - `created_time_ms` bigint unsigned NOT NULL, - PRIMARY KEY (`app_id`,`tenant_id`,`user_id`,`created_time_ms`), - KEY `app_id` (`app_id`,`user_id`), - FOREIGN KEY (`app_id`, `user_id`) REFERENCES `totp_users` (`app_id`, `user_id`) ON DELETE CASCADE, - FOREIGN KEY (`app_id`, `tenant_id`) REFERENCES `tenants` (`app_id`, `tenant_id`) ON DELETE CASCADE -); - -CREATE INDEX `totp_used_codes_expiry_time_ms_index` ON `totp_used_codes` (`app_id`,`tenant_id`,`expiry_time_ms`); -``` - -:::tip -You also have the option to [rename these tables](./rename-database-tables). -::: - -## 4) Test the connection 🤞 -To test, start SuperTokens and run the following query in your database -```sql -SELECT * FROM key_value; -``` -If you see at least one row, it means that the connection has been successfully completed! 🥳🎉 - -:::tip blog -We also have [a blog post writeup](https://supertokens.com/blog/connect-supertokens-to-database) highlighting all the steps in more detail for different scenarios. -::: diff --git a/v2/thirdpartypasswordless/custom-ui/init/database-setup/postgresql.mdx b/v2/thirdpartypasswordless/custom-ui/init/database-setup/postgresql.mdx index abaea10fc..8375aa15f 100644 --- a/v2/thirdpartypasswordless/custom-ui/init/database-setup/postgresql.mdx +++ b/v2/thirdpartypasswordless/custom-ui/init/database-setup/postgresql.mdx @@ -4,601 +4,8 @@ title: If using PostgreSQL hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import SelfHostedTabs from "/src/components/tabs/SelfHostedTabs" -import TabItem from '@theme/TabItem'; -# PostgreSQL setup + -:::important -This is needed only if you are running the SuperTokens core yourself. - -The minimum required version is **PostgreSQL 9.6**. -::: - -:::tip blog -We also have [a blog post writeup](https://supertokens.com/blog/connect-supertokens-to-database) highlighting all the steps in more detail for different scenarios. -::: - -## 1) Create a database 🛠️ - -```sql -CREATE DATABASE supertokens; -``` -You can skip this step if you want SuperTokens to write to your own database. In this case, you will need to provide your database name as shown in the step below. - -## 2) Connect SuperTokens to your database 🔌 - - - - -:::caution -Host being `localhost` / `127.0.0.1` will not work in a docker image. Instead, please provide the database's local / public hostname or IP address. - -You also need to make the database listen on all the IP's of the local machine. -This can be done by editing the `postgresql.conf` config file and setting the value of `listen_addresses` to `0.0.0.0`. -::: - -```bash - -docker run \ - -p 3567:3567 \ - // highlight-next-line - -e POSTGRESQL_CONNECTION_URI="postgresql://username:pass@host/dbName" \ - -d registry.supertokens.io/supertokens/supertokens-postgresql - -# OR - -docker run \ - -p 3567:3567 \ - // highlight-start - -e POSTGRESQL_USER="username" \ - -e POSTGRESQL_PASSWORD="password" \ - -e POSTGRESQL_HOST="host" \ - -e POSTGRESQL_PORT="5432" \ - -e POSTGRESQL_DATABASE_NAME="supertokens" \ - // highlight-end - -d registry.supertokens.io/supertokens/supertokens-postgresql -``` - -:::tip -You can also provide the table schema by providing the `POSTGRESQL_TABLE_SCHEMA` option. -::: - - - - - -```yaml -# You need to add the following to the config.yaml file. -# The file path can be found by running the "supertokens --help" command - -postgresql_connection_uri: "postgresql://username:pass@host/dbName" - -# OR - -postgresql_user: "username" - -postgresql_password: "password" - -postgresql_host: "host" - -postgresql_port: "5432" - -postgresql_database_name: "supertokens" -``` - -- You can also provide the table schema by providing the `postgresql_table_schema` option. - - - - - -## 3) Create tables 👩‍💻👨‍💻 - -:::note -This happens automatically, unless you provide a PostgreSQL user that doesn't have table creation permission. -::: - -```sql -CREATE TABLE apps ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - created_at_time BIGINT, - CONSTRAINT apps_pkey PRIMARY KEY (app_id) -); - -CREATE TABLE tenants ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - created_at_time BIGINT, - CONSTRAINT tenants_pkey PRIMARY KEY (app_id, tenant_id), - CONSTRAINT tenants_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX tenants_app_id_index ON tenants (app_id); - -CREATE TABLE tenant_configs ( - connection_uri_domain VARCHAR(256) DEFAULT '' NOT NULL, - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - core_config TEXT, - email_password_enabled BOOLEAN, - passwordless_enabled BOOLEAN, - third_party_enabled BOOLEAN, - is_first_factors_null BOOLEAN, - CONSTRAINT tenant_configs_pkey PRIMARY KEY (connection_uri_domain, app_id, tenant_id) -); - -CREATE TABLE tenant_thirdparty_providers ( - connection_uri_domain VARCHAR(256) DEFAULT '' NOT NULL, - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - third_party_id VARCHAR(28) NOT NULL, - name VARCHAR(64), - authorization_endpoint TEXT, - authorization_endpoint_query_params TEXT, - token_endpoint TEXT, - token_endpoint_body_params TEXT, - user_info_endpoint TEXT, - user_info_endpoint_query_params TEXT, - user_info_endpoint_headers TEXT, - jwks_uri TEXT, - oidc_discovery_endpoint TEXT, - require_email BOOLEAN, - user_info_map_from_id_token_payload_user_id VARCHAR(64), - user_info_map_from_id_token_payload_email VARCHAR(64), - user_info_map_from_id_token_payload_email_verified VARCHAR(64), - user_info_map_from_user_info_endpoint_user_id VARCHAR(64), - user_info_map_from_user_info_endpoint_email VARCHAR(64), - user_info_map_from_user_info_endpoint_email_verified VARCHAR(64), - CONSTRAINT tenant_thirdparty_providers_pkey PRIMARY KEY (connection_uri_domain, app_id, tenant_id, third_party_id), - CONSTRAINT tenant_thirdparty_providers_tenant_id_fkey FOREIGN KEY (connection_uri_domain, app_id, tenant_id) REFERENCES public.tenant_configs(connection_uri_domain, app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX tenant_thirdparty_providers_tenant_id_index ON tenant_thirdparty_providers (connection_uri_domain, app_id, tenant_id); - -CREATE TABLE tenant_thirdparty_provider_clients ( - connection_uri_domain VARCHAR(256) DEFAULT '' NOT NULL, - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - third_party_id VARCHAR(28) NOT NULL, - client_type VARCHAR(64) DEFAULT '' NOT NULL, - client_id VARCHAR(256) NOT NULL, - client_secret TEXT, - scope VARCHAR(128)[], - force_pkce BOOLEAN, - additional_config TEXT, - CONSTRAINT tenant_thirdparty_provider_clients_pkey PRIMARY KEY (connection_uri_domain, app_id, tenant_id, third_party_id, client_type), - CONSTRAINT tenant_thirdparty_provider_clients_third_party_id_fkey FOREIGN KEY (connection_uri_domain, app_id, tenant_id, third_party_id) REFERENCES public.tenant_thirdparty_providers(connection_uri_domain, app_id, tenant_id, third_party_id) ON DELETE CASCADE -); - -CREATE INDEX tenant_thirdparty_provider_clients_third_party_id_index ON tenant_thirdparty_provider_clients (connection_uri_domain, app_id, tenant_id, third_party_id); - -CREATE TABLE tenant_first_factors ( - connection_uri_domain VARCHAR(256) DEFAULT '' NOT NULL, - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - factor_id VARCHAR(128), - CONSTRAINT tenant_first_factors_pkey PRIMARY KEY (connection_uri_domain, app_id, tenant_id, factor_id), - CONSTRAINT tenant_first_factors_tenant_id_fkey FOREIGN KEY (connection_uri_domain, app_id, tenant_id) REFERENCES public.tenant_configs(connection_uri_domain, app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX IF NOT EXISTS tenant_first_factors_tenant_id_index ON tenant_first_factors (connection_uri_domain, app_id, tenant_id); - -CREATE TABLE tenant_required_secondary_factors ( - connection_uri_domain VARCHAR(256) DEFAULT '' NOT NULL, - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - factor_id VARCHAR(128), - CONSTRAINT tenant_required_secondary_factors_pkey PRIMARY KEY (connection_uri_domain, app_id, tenant_id, factor_id), - CONSTRAINT tenant_required_secondary_factors_tenant_id_fkey FOREIGN KEY (connection_uri_domain, app_id, tenant_id) REFERENCES public.tenant_configs(connection_uri_domain, app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX IF NOT EXISTS tenant_default_required_factor_ids_tenant_id_index ON tenant_required_secondary_factors (connection_uri_domain, app_id, tenant_id); - -CREATE TABLE key_value ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - name VARCHAR(128) NOT NULL, - value TEXT, - created_at_time BIGINT, - CONSTRAINT key_value_pkey PRIMARY KEY (app_id, tenant_id, name), - CONSTRAINT key_value_tenant_id_fkey FOREIGN KEY (app_id, tenant_id) REFERENCES public.tenants(app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX key_value_tenant_id_index ON key_value (app_id, tenant_id); - -CREATE TABLE app_id_to_user_id ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - recipe_id VARCHAR(128) NOT NULL, - primary_or_recipe_user_id CHAR(36) NOT NULL, - is_linked_or_is_a_primary_user BOOLEAN NOT NULL DEFAULT FALSE, - CONSTRAINT app_id_to_user_id_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT app_id_to_user_id_primary_or_recipe_user_id_fkey FOREIGN KEY(app_id, primary_or_recipe_user_id) REFERENCES app_id_to_user_id (app_id, user_id) ON DELETE CASCADE, - CONSTRAINT app_id_to_user_id_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX app_id_to_user_id_app_id_index ON app_id_to_user_id (app_id); - -CREATE INDEX app_id_to_user_id_primary_user_id_index ON app_id_to_user_id (primary_or_recipe_user_id, app_id); - -CREATE TABLE all_auth_recipe_users ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - primary_or_recipe_user_id CHAR(36) NOT NULL, - is_linked_or_is_a_primary_user BOOLEAN NOT NULL DEFAULT FALSE, - recipe_id VARCHAR(128) NOT NULL, - time_joined BIGINT NOT NULL, - primary_or_recipe_user_time_joined BIGINT NOT NULL, - CONSTRAINT all_auth_recipe_users_pkey PRIMARY KEY (app_id, tenant_id, user_id), - CONSTRAINT all_auth_recipe_users_tenant_id_fkey FOREIGN KEY (app_id, tenant_id) REFERENCES public.tenants(app_id, tenant_id) ON DELETE CASCADE, - CONSTRAINT all_auth_recipe_users_primary_or_recipe_user_id_fkey FOREIGN KEY(app_id, primary_or_recipe_user_id) REFERENCES public.app_id_to_user_id (app_id, user_id) ON DELETE CASCADE, - CONSTRAINT all_auth_recipe_users_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.app_id_to_user_id(app_id, user_id) ON DELETE CASCADE -); - -CREATE INDEX all_auth_recipe_users_pagination_index1 ON all_auth_recipe_users - (app_id, tenant_id, primary_or_recipe_user_time_joined DESC, primary_or_recipe_user_id DESC); - -CREATE INDEX all_auth_recipe_users_pagination_index2 ON all_auth_recipe_users - (app_id, tenant_id, primary_or_recipe_user_time_joined ASC, primary_or_recipe_user_id DESC); - -CREATE INDEX all_auth_recipe_users_pagination_index3 ON all_auth_recipe_users - (recipe_id, app_id, tenant_id, primary_or_recipe_user_time_joined DESC, primary_or_recipe_user_id DESC); - - -CREATE INDEX all_auth_recipe_users_pagination_index4 ON all_auth_recipe_users - (recipe_id, app_id, tenant_id, primary_or_recipe_user_time_joined ASC, primary_or_recipe_user_id DESC); - - -CREATE INDEX all_auth_recipe_users_primary_user_id_index ON all_auth_recipe_users - (primary_or_recipe_user_id, app_id); - -CREATE INDEX all_auth_recipe_users_recipe_id_index ON all_auth_recipe_users - (app_id, recipe_id, tenant_id); - -CREATE INDEX all_auth_recipe_user_id_index ON all_auth_recipe_users (app_id, user_id); - -CREATE INDEX all_auth_recipe_tenant_id_index ON all_auth_recipe_users (app_id, tenant_id); - -CREATE TABLE userid_mapping ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - supertokens_user_id character(36) NOT NULL, - external_user_id VARCHAR(128) NOT NULL, - external_user_id_info TEXT, - CONSTRAINT userid_mapping_external_user_id_key UNIQUE (app_id, external_user_id), - CONSTRAINT userid_mapping_pkey PRIMARY KEY (app_id, supertokens_user_id, external_user_id), - CONSTRAINT userid_mapping_supertokens_user_id_key UNIQUE (app_id, supertokens_user_id), - CONSTRAINT userid_mapping_supertokens_user_id_fkey FOREIGN KEY (app_id, supertokens_user_id) REFERENCES public.app_id_to_user_id(app_id, user_id) ON DELETE CASCADE -); - -CREATE INDEX userid_mapping_supertokens_user_id_index ON userid_mapping (app_id, supertokens_user_id); - -CREATE TABLE dashboard_users ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - email VARCHAR(256) NOT NULL, - password_hash VARCHAR(256) NOT NULL, - time_joined BIGINT NOT NULL, - CONSTRAINT dashboard_users_email_key UNIQUE (app_id, email), - CONSTRAINT dashboard_users_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT dashboard_users_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX dashboard_users_app_id_index ON dashboard_users (app_id); - -CREATE TABLE dashboard_user_sessions ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - session_id character(36) NOT NULL, - user_id character(36) NOT NULL, - time_created BIGINT NOT NULL, - expiry BIGINT NOT NULL, - CONSTRAINT dashboard_user_sessions_pkey PRIMARY KEY (app_id, session_id), - CONSTRAINT dashboard_user_sessions_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.dashboard_users(app_id, user_id) ON UPDATE CASCADE ON DELETE CASCADE -); - -CREATE INDEX dashboard_user_sessions_expiry_index ON dashboard_user_sessions (expiry); - -CREATE INDEX dashboard_user_sessions_user_id_index ON dashboard_user_sessions (app_id, user_id); - -CREATE TABLE session_access_token_signing_keys ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - created_at_time BIGINT NOT NULL, - value TEXT, - CONSTRAINT session_access_token_signing_keys_pkey PRIMARY KEY (app_id, created_at_time), - CONSTRAINT session_access_token_signing_keys_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX access_token_signing_keys_app_id_index ON session_access_token_signing_keys (app_id); - -CREATE TABLE session_info ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - session_handle VARCHAR(255) NOT NULL, - user_id VARCHAR(128) NOT NULL, - refresh_token_hash_2 VARCHAR(128) NOT NULL, - session_data TEXT, - expires_at BIGINT NOT NULL, - created_at_time BIGINT NOT NULL, - jwt_user_payload TEXT, - use_static_key BOOLEAN NOT NULL, - CONSTRAINT session_info_pkey PRIMARY KEY (app_id, tenant_id, session_handle), - CONSTRAINT session_info_tenant_id_fkey FOREIGN KEY (app_id, tenant_id) REFERENCES public.tenants(app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX session_expiry_index ON session_info (expires_at); - -CREATE INDEX session_info_tenant_id_index ON session_info (app_id, tenant_id); - -CREATE TABLE user_last_active ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - last_active_time BIGINT, - CONSTRAINT user_last_active_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT user_last_active_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX user_last_active_app_id_index ON user_last_active (app_id); - -CREATE TABLE emailpassword_users ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - email VARCHAR(256) NOT NULL, - password_hash VARCHAR(256) NOT NULL, - time_joined BIGINT NOT NULL, - CONSTRAINT emailpassword_users_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT emailpassword_users_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.app_id_to_user_id(app_id, user_id) ON DELETE CASCADE -); - -CREATE TABLE emailpassword_user_to_tenant ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - email VARCHAR(256) NOT NULL, - CONSTRAINT emailpassword_user_to_tenant_email_key UNIQUE (app_id, tenant_id, email), - CONSTRAINT emailpassword_user_to_tenant_pkey PRIMARY KEY (app_id, tenant_id, user_id), - CONSTRAINT emailpassword_user_to_tenant_user_id_fkey FOREIGN KEY (app_id, tenant_id, user_id) REFERENCES public.all_auth_recipe_users(app_id, tenant_id, user_id) ON DELETE CASCADE -); - -CREATE TABLE emailpassword_pswd_reset_tokens ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - token VARCHAR(128) NOT NULL, - token_expiry BIGINT NOT NULL, - email VARCHAR(256), - CONSTRAINT emailpassword_pswd_reset_tokens_pkey PRIMARY KEY (app_id, user_id, token), - CONSTRAINT emailpassword_pswd_reset_tokens_token_key UNIQUE (token), - CONSTRAINT emailpassword_pswd_reset_tokens_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.app_id_to_user_id(app_id, user_id) ON UPDATE CASCADE ON DELETE CASCADE -); - -CREATE INDEX emailpassword_password_reset_token_expiry_index ON emailpassword_pswd_reset_tokens (token_expiry); - -CREATE INDEX emailpassword_pswd_reset_tokens_user_id_index ON emailpassword_pswd_reset_tokens (app_id, user_id); - -CREATE TABLE emailverification_verified_emails ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - email VARCHAR(256) NOT NULL, - CONSTRAINT emailverification_verified_emails_pkey PRIMARY KEY (app_id, user_id, email), - CONSTRAINT emailverification_verified_emails_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX emailverification_verified_emails_app_id_index ON emailverification_verified_emails (app_id); - -CREATE TABLE emailverification_tokens ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - email VARCHAR(256) NOT NULL, - token VARCHAR(128) NOT NULL, - token_expiry BIGINT NOT NULL, - CONSTRAINT emailverification_tokens_pkey PRIMARY KEY (app_id, tenant_id, user_id, email, token), - CONSTRAINT emailverification_tokens_token_key UNIQUE (token), - CONSTRAINT emailverification_tokens_tenant_id_fkey FOREIGN KEY (app_id, tenant_id) REFERENCES public.tenants(app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX emailverification_tokens_index ON emailverification_tokens (token_expiry); - -CREATE INDEX emailverification_tokens_tenant_id_index ON emailverification_tokens (app_id, tenant_id); - -CREATE TABLE thirdparty_users ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - third_party_id VARCHAR(28) NOT NULL, - third_party_user_id VARCHAR(256) NOT NULL, - user_id character(36) NOT NULL, - email VARCHAR(256) NOT NULL, - time_joined BIGINT NOT NULL, - CONSTRAINT thirdparty_users_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT thirdparty_users_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.app_id_to_user_id(app_id, user_id) ON DELETE CASCADE -); - -CREATE INDEX thirdparty_users_email_index ON thirdparty_users (app_id, email); - -CREATE INDEX thirdparty_users_thirdparty_user_id_index ON thirdparty_users (app_id, third_party_id, third_party_user_id); - -CREATE TABLE thirdparty_user_to_tenant ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - third_party_id VARCHAR(28) NOT NULL, - third_party_user_id VARCHAR(256) NOT NULL, - CONSTRAINT thirdparty_user_to_tenant_pkey PRIMARY KEY (app_id, tenant_id, user_id), - CONSTRAINT thirdparty_user_to_tenant_third_party_user_id_key UNIQUE (app_id, tenant_id, third_party_id, third_party_user_id), - CONSTRAINT thirdparty_user_to_tenant_user_id_fkey FOREIGN KEY (app_id, tenant_id, user_id) REFERENCES public.all_auth_recipe_users(app_id, tenant_id, user_id) ON DELETE CASCADE -); - -CREATE TABLE passwordless_users ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - email VARCHAR(256), - phone_number VARCHAR(256), - time_joined BIGINT NOT NULL, - CONSTRAINT passwordless_users_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT passwordless_users_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.app_id_to_user_id(app_id, user_id) ON DELETE CASCADE -); - -CREATE TABLE passwordless_user_to_tenant ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id character(36) NOT NULL, - email VARCHAR(256), - phone_number VARCHAR(256), - CONSTRAINT passwordless_user_to_tenant_email_key UNIQUE (app_id, tenant_id, email), - CONSTRAINT passwordless_user_to_tenant_phone_number_key UNIQUE (app_id, tenant_id, phone_number), - CONSTRAINT passwordless_user_to_tenant_pkey PRIMARY KEY (app_id, tenant_id, user_id), - CONSTRAINT passwordless_user_to_tenant_user_id_fkey FOREIGN KEY (app_id, tenant_id, user_id) REFERENCES public.all_auth_recipe_users(app_id, tenant_id, user_id) ON DELETE CASCADE -); - -CREATE TABLE passwordless_devices ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - device_id_hash character(44) NOT NULL, - email VARCHAR(256), - phone_number VARCHAR(256), - link_code_salt character(44) NOT NULL, - failed_attempts integer NOT NULL, - CONSTRAINT passwordless_devices_pkey PRIMARY KEY (app_id, tenant_id, device_id_hash), - CONSTRAINT passwordless_devices_tenant_id_fkey FOREIGN KEY (app_id, tenant_id) REFERENCES public.tenants(app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX passwordless_devices_email_index ON passwordless_devices (app_id, tenant_id, email); - -CREATE INDEX passwordless_devices_phone_number_index ON passwordless_devices (app_id, tenant_id, phone_number); - -CREATE INDEX passwordless_devices_tenant_id_index ON passwordless_devices (app_id, tenant_id); - -CREATE TABLE passwordless_codes ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - code_id character(36) NOT NULL, - device_id_hash character(44) NOT NULL, - link_code_hash character(44) NOT NULL, - created_at BIGINT NOT NULL, - CONSTRAINT passwordless_codes_link_code_hash_key UNIQUE (app_id, tenant_id, link_code_hash), - CONSTRAINT passwordless_codes_pkey PRIMARY KEY (app_id, tenant_id, code_id), - CONSTRAINT passwordless_codes_device_id_hash_fkey FOREIGN KEY (app_id, tenant_id, device_id_hash) REFERENCES public.passwordless_devices(app_id, tenant_id, device_id_hash) ON UPDATE CASCADE ON DELETE CASCADE -); - -CREATE INDEX passwordless_codes_created_at_index ON passwordless_codes (app_id, tenant_id, created_at); - -CREATE INDEX passwordless_codes_device_id_hash_index ON passwordless_codes (app_id, tenant_id, device_id_hash); - -CREATE TABLE jwt_signing_keys ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - key_id VARCHAR(255) NOT NULL, - key_string TEXT NOT NULL, - algorithm VARCHAR(10) NOT NULL, - created_at BIGINT, - CONSTRAINT jwt_signing_keys_pkey PRIMARY KEY (app_id, key_id), - CONSTRAINT jwt_signing_keys_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX jwt_signing_keys_app_id_index ON jwt_signing_keys (app_id); - -CREATE TABLE user_metadata ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - user_metadata TEXT NOT NULL, - CONSTRAINT user_metadata_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT user_metadata_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX user_metadata_app_id_index ON user_metadata (app_id); - -CREATE TABLE roles ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - role VARCHAR(255) NOT NULL, - CONSTRAINT roles_pkey PRIMARY KEY (app_id, role), - CONSTRAINT roles_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX roles_app_id_index ON roles (app_id); - -CREATE TABLE role_permissions ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - role VARCHAR(255) NOT NULL, - permission VARCHAR(255) NOT NULL, - CONSTRAINT role_permissions_pkey PRIMARY KEY (app_id, role, permission), - CONSTRAINT role_permissions_role_fkey FOREIGN KEY (app_id, role) REFERENCES public.roles(app_id, role) ON DELETE CASCADE -); - -CREATE INDEX role_permissions_permission_index ON role_permissions (app_id, permission); - -CREATE INDEX role_permissions_role_index ON role_permissions (app_id, role); - -CREATE TABLE user_roles ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - role VARCHAR(255) NOT NULL, - CONSTRAINT user_roles_pkey PRIMARY KEY (app_id, tenant_id, user_id, role), - CONSTRAINT user_roles_tenant_id_fkey FOREIGN KEY (app_id, tenant_id) REFERENCES public.tenants(app_id, tenant_id) ON DELETE CASCADE -); - -CREATE INDEX user_roles_role_index ON user_roles (app_id, tenant_id, role); - -CREATE INDEX user_roles_tenant_id_index ON user_roles (app_id, tenant_id); - -CREATE INDEX user_roles_app_id_role_index ON user_roles (app_id, role); - -CREATE TABLE totp_users ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - CONSTRAINT totp_users_pkey PRIMARY KEY (app_id, user_id), - CONSTRAINT totp_users_app_id_fkey FOREIGN KEY (app_id) REFERENCES public.apps(app_id) ON DELETE CASCADE -); - -CREATE INDEX totp_users_app_id_index ON totp_users (app_id); - -CREATE TABLE totp_user_devices ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - device_name VARCHAR(256) NOT NULL, - secret_key VARCHAR(256) NOT NULL, - period integer NOT NULL, - skew integer NOT NULL, - verified BOOLEAN NOT NULL, - created_at BIGINT, - CONSTRAINT totp_user_devices_pkey PRIMARY KEY (app_id, user_id, device_name), - CONSTRAINT totp_user_devices_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.totp_users(app_id, user_id) ON DELETE CASCADE -); - -CREATE INDEX totp_user_devices_user_id_index ON totp_user_devices (app_id, user_id); - -CREATE TABLE totp_used_codes ( - app_id VARCHAR(64) DEFAULT 'public' NOT NULL, - tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL, - user_id VARCHAR(128) NOT NULL, - code VARCHAR(8) NOT NULL, - is_valid BOOLEAN NOT NULL, - expiry_time_ms BIGINT NOT NULL, - created_time_ms BIGINT NOT NULL, - CONSTRAINT totp_used_codes_pkey PRIMARY KEY (app_id, tenant_id, user_id, created_time_ms), - CONSTRAINT totp_used_codes_tenant_id_fkey FOREIGN KEY (app_id, tenant_id) REFERENCES public.tenants(app_id, tenant_id) ON DELETE CASCADE, - CONSTRAINT totp_used_codes_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES public.totp_users(app_id, user_id) ON DELETE CASCADE -); - -CREATE INDEX totp_used_codes_expiry_time_ms_index ON totp_used_codes (app_id, tenant_id, expiry_time_ms); - -CREATE INDEX totp_used_codes_tenant_id_index ON totp_used_codes (app_id, tenant_id); - -CREATE INDEX totp_used_codes_user_id_index ON totp_used_codes (app_id, user_id); -``` - -:::tip -You also have the option to [rename these tables](./rename-database-tables). -::: - -## 4) Test the connection 🤞 -To test, start SuperTokens and run the following query in your database -```sql -SELECT * FROM key_value; -``` -If you see at least one row, it means that the connection has been successfully completed! 🥳🎉 - -:::tip blog -We also have [a blog post writeup](https://supertokens.com/blog/connect-supertokens-to-database) highlighting all the steps in more detail for different scenarios. -::: diff --git a/v2/thirdpartypasswordless/custom-ui/init/database-setup/rename-database-tables.mdx b/v2/thirdpartypasswordless/custom-ui/init/database-setup/rename-database-tables.mdx index 93f84ccc6..e02f73ba3 100644 --- a/v2/thirdpartypasswordless/custom-ui/init/database-setup/rename-database-tables.mdx +++ b/v2/thirdpartypasswordless/custom-ui/init/database-setup/rename-database-tables.mdx @@ -4,95 +4,8 @@ title: Rename database tables hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import SelfHostedTabs from "/src/components/tabs/SelfHostedTabs" -import TabItem from '@theme/TabItem'; -# Rename database tables + -:::caution -If you already have tables created by SuperTokens, and then you rename them, SuperTokens will create new tables. So please be sure to migrate the data from the existing one to the new one. -::: - -You can add a prefix to all table names that are managed by SuperTokens. This way, all of them will be renamed in a way that have no clashes with your tables. - -For example, two tables created by SuperTokens are called `emailpassword_users` and `thirdparty_users`. If you add a prefix to them (something like `"my_prefix"`), then the tables will be renamed to `my_prefix_emailpassword_users` and `my_prefix_thirdparty_users`. - -## For MySQL - - - - -```bash -docker run \ - -p 3567:3567 \ - // highlight-next-line - -e MYSQL_TABLE_NAMES_PREFIX="my_prefix" \ - -d registry.supertokens.io/supertokens/supertokens-mysql -``` - - - - -```yaml -# You need to add the following to the config.yaml file. -# The file path can be found by running the "supertokens --help" command - -mysql_table_names_prefix: "my_prefix" -``` - - - -## For PostgreSQL - - - - -```bash -docker run \ - -p 3567:3567 \ - // highlight-next-line - -e POSTGRESQL_TABLE_NAMES_PREFIX="my_prefix" \ - -d registry.supertokens.io/supertokens/supertokens-postgresql -``` - - - - -```yaml -# You need to add the following to the config.yaml file. -# The file path can be found by running the "supertokens --help" command - -postgresql_table_names_prefix: "my_prefix" -``` - - - -## For MongoDB - - - - -```bash -docker run \ - -p 3567:3567 \ - // highlight-next-line - -e MONGODB_COLLECTION_NAMES_PREFIX="my_prefix" \ - -d registry.supertokens.io/supertokens/supertokens-mongodb -``` - - - - - -```yaml -# You need to add the following to the config.yaml file. -# The file path can be found by running the "supertokens --help" command - -mongodb_collection_names_prefix: "my_prefix" -``` - - - diff --git a/v2/thirdpartypasswordless/custom-ui/init/frontend.mdx b/v2/thirdpartypasswordless/custom-ui/init/frontend.mdx index 170db898e..bac6ce88b 100644 --- a/v2/thirdpartypasswordless/custom-ui/init/frontend.mdx +++ b/v2/thirdpartypasswordless/custom-ui/init/frontend.mdx @@ -4,323 +4,8 @@ title: "Step 1: Frontend" hide_title: true --- -import { PasswordlessFrontendForm } from "/src/components/snippetConfigForm/passwordlessFrontendForm"; +import Redirector from '/src/components/Redirector'; + -import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" -import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" -import TabItem from '@theme/TabItem'; -import {Question, Answer}from "/src/components/question" -import AppInfoForm from "/src/components/appInfoForm" -import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" -import WebJsInjector from "/src/components/webJsInjector" - -# Frontend Integration - -## Supported frameworks - -import TechStackSupport from "../../../community/reusableMD/supported-tech-stacks-frontend-custom-ui.mdx" - - - -## 1) Install - - - - - - - -```bash -npm i -s supertokens-web-js -``` - - - - -You need to add all of the following scripts to your app - - - -```bash - - - - - -``` - - - - - - - - - - -:::info - -If you want to implement a common authencation experience for both web and mobile, please look at our [**Unified Login guide**](/docs/unified-login/introduction). - -::: - - - - - -```bash -npm i -s supertokens-react-native -# IMPORTANT: If you already have @react-native-async-storage/async-storage as a dependency, make sure the version is 1.12.1 or higher -npm i -s @react-native-async-storage/async-storage -``` - - - - - -Add to your `settings.gradle`: -```bash -dependencyResolutionManagement { - ... - repositories { - ... - maven { url 'https://jitpack.io' } - } -} -``` - -Add the following to you app level's `build.gradle`: -```bash -implementation 'com.github.supertokens:supertokens-android:X.Y.Z' -``` - -You can find the latest version of the SDK [here](https://github.com/supertokens/supertokens-android/releases) (ignore the `v` prefix in the releases). - - - - - -#### Using Cocoapods - -Add the Cocoapod dependency to your Podfile - -```bash -pod 'SuperTokensIOS' -``` - -#### Using Swift Package Manager - -Follow the [official documentation](https://developer.apple.com/documentation/xcode/adding-package-dependencies-to-your-app) to learn how to use Swift Package Manager to add dependencies to your project. - -When adding the dependency use the `master` branch after you enter the supertokens-ios repository URL: - -```bash -https://github.com/supertokens/supertokens-ios -``` - - - - - -Add the dependency to your pubspec.yaml - -```bash -supertokens_flutter: ^X.Y.Z -``` - -You can find the latest version of the SDK [here](https://github.com/supertokens/supertokens-flutter/releases) (ignore the `v` prefix in the releases). - - - - - - - - - -## 2) Call the `init` function - - - - - - - - - -Call the following `init` function at the start of your app (ideally on the global scope). - -```tsx -import SuperTokens from 'supertokens-web-js'; -import Session from 'supertokens-web-js/recipe/session'; -import ThirdParty from 'supertokens-web-js/recipe/thirdparty' -import Passwordless from 'supertokens-web-js/recipe/passwordless' - -SuperTokens.init({ - appInfo: { - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}", - appName: "...", - }, - recipeList: [ - Session.init(), - Passwordless.init(), - ThirdParty.init() - ], -}); -``` - - - - - - - - -Call the following `init` function at the start of your app (ideally on the global scope). - -```tsx -import supertokens from "supertokens-web-js-script"; -import supertokensSession from "supertokens-web-js-script/recipe/session"; -import supertokensPasswordless from 'supertokens-web-js-script/recipe/passwordless' -import supertokensThirdParty from 'supertokens-web-js-script/recipe/thirdparty' -supertokens.init({ - appInfo: { - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}", - appName: "...", - }, - recipeList: [ - supertokensSession.init(), - supertokensPasswordless.init(), - supertokensThirdParty.init(), - ], -}); -``` - - - - - - - - - - - - - - - - -Call the following `init` function at the start of your app (ideally on the global scope). - -```tsx -import SuperTokens from 'supertokens-react-native'; - -SuperTokens.init({ - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}", -}); -``` - - - - - - - - - -Add the `SuperTokens.init` function call at the start of your application. - -```kotlin -import android.app.Application -import com.supertokens.session.SuperTokens - -class MainApplication: Application() { - override fun onCreate() { - super.onCreate() - - SuperTokens.Builder(this, "^{form_apiDomain}") - .apiBasePath("^{form_apiBasePath}") - .build() - } -} -``` - - - - - - - - - -Add the `SuperTokens.initialize` function call at the start of your application. - -```swift -import UIKit -import SuperTokensIOS - -fileprivate class ApplicationDelegate: UIResponder, UIApplicationDelegate { - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - do { - try SuperTokens.initialize( - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}" - ) - } catch SuperTokensError.initError(let message) { - // TODO: Handle initialization error - } catch { - // Some other error - } - - return true - } - -} -``` - - - - - - - - - -Add the `SuperTokens.init` function call at the start of your application. - -```dart -import 'package:supertokens_flutter/supertokens.dart'; - -void main() { - SuperTokens.init( - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}", - ); -} -``` - - - - - - - - - - - -## What to do next? -The above code snippet sets up session management network interceptors on the frontend. Our frontend SDK will now be able to automatically save and add session tokens to each request to your API layer and also do auto session refreshing. - -The next steps are to: -- Step 2: setup the backend SDK in your API layer -- Step 3: Setup the SuperTokens core (sign up for managed service, or self host it) -- Step 4: Enable the user management dashboard -- Use the frontend SDK's helper functions to build your own UI - follow along the docs in the "Using your own UI" section and you will find docs for this after "Step 4". diff --git a/v2/thirdpartypasswordless/custom-ui/init/user-management-dashboard/managing-user-roles-and-permissions.mdx b/v2/thirdpartypasswordless/custom-ui/init/user-management-dashboard/managing-user-roles-and-permissions.mdx index ec916c7a2..ae431bb4c 100644 --- a/v2/thirdpartypasswordless/custom-ui/init/user-management-dashboard/managing-user-roles-and-permissions.mdx +++ b/v2/thirdpartypasswordless/custom-ui/init/user-management-dashboard/managing-user-roles-and-permissions.mdx @@ -4,157 +4,7 @@ title: "Managing user roles and permissions" hide_title: true --- +import Redirector from '/src/components/Redirector'; - - - + -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import TabItem from '@theme/TabItem'; - -# Managing user roles and permissions - -You can manage [user roles and permissions](/docs/userroles/introduction) of your app from the user management dashboard. - -## Initialisation - -To begin configuring user roles and permissions on the user management dashboard, start by initializing the "UserRoles" recipe in the `recipeList` on the backend. - - - - -```tsx -import SuperTokens from "supertokens-node"; -import Dashboard from "supertokens-node/recipe/dashboard"; -import UserRoles from "supertokens-node/recipe/userroles" - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // TODO: Initialise other recipes if needed. - Dashboard.init(), - // highlight-start - UserRoles.init() - // highlight-end - ], -}); -``` -:::important Note - -Please note that the capability to manage roles and permissions from the user management dashboard is available only from Node SDK version `v16.6.0` onwards. - -::: - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/dashboard" - "github.com/supertokens/supertokens-golang/recipe/userroles" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - supertokens.Init(supertokens.TypeInput{ - RecipeList: []supertokens.Recipe{ - // TODO: Initialise other recipes if needed - dashboard.Init(nil), - // highlight-start - userroles.Init(nil), - // highlight-end - }, - }); -} -``` - - - - - -```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import dashboard, userroles - -init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore - recipe_list=[ - # TODO: Initialise other recipes if needed. - dashboard.init(), - # highlight-start - userroles.init() - # highlight-end - ] -) -``` - - - - - - - -## Managing roles and permissions - -When you first use the `UserRoles` recipe, the list of roles will be empty. To create roles, simply click on the "Add Role" button. - -No roles created - -This action will open a modal, enabling you to create a role along with its associated permissions. Permissions are essentially a list of strings assigned to a specific role. - -Create role - -After creating a role, the UI should display a list of all roles in your app. - -Roles list - -You can now preview the role you created by clicking on the role row. The modal provides options to edit or delete the role. - -Preview role - -## Manging roles and users - -To assign a specific role to a user, start by finding the user in the dashboard. Upon clicking the user, navigate to the user details page where you'll find a section for user roles. - -If the selected user is associated with multiple tenants, you can choose a 'tenantId' from the dropdown menu to specify the tenant for which you'd like to assign roles. - -Select tenant - -Click the edit button to start assigning roles. Then, select the "Assign Role" button, and a modal will appear with a list of available roles for assignment to this user. - -Assign role - -To remove a role assigned to a user, simply click on the "X" icon next to that specific role. - -View assigned role - - - - -:::important - -Our Python SDK doesn't currently support roles management via the user management UI. Instead, you can use the functions exposed by the SDK to manage roles and permissions. Check out the guide [here](/docs/userroles/introduction). - -::: - - - - - -:::important - -Our Golang SDK doesn't currently support roles management via the user management UI. Instead, you can use the functions exposed by the SDK to manage roles and permissions. Check out the guide [here](/docs/userroles/introduction). - -::: - - - - - - - diff --git a/v2/thirdpartypasswordless/custom-ui/init/user-management-dashboard/setup.mdx b/v2/thirdpartypasswordless/custom-ui/init/user-management-dashboard/setup.mdx index 0c108af83..ce23dfa60 100644 --- a/v2/thirdpartypasswordless/custom-ui/init/user-management-dashboard/setup.mdx +++ b/v2/thirdpartypasswordless/custom-ui/init/user-management-dashboard/setup.mdx @@ -4,329 +4,7 @@ title: "Setting up the dashboard" hide_title: true --- -# Setting up the Dashboard +import Redirector from '/src/components/Redirector'; - - - + -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import AppInfoForm from "/src/components/appInfoForm" -import CustomAdmonition from "/src/components/customAdmonition" - - -## Architecture - - - -Flowchart of architecture when using SuperTokens managed service - - -Flowchart of architecture when self-hosting SuperTokens - - - -The Backend SDK serves the user management dashboard which can be accessed on the `/auth/dashboard` path of your API domain. - - -## Initialise the dashboard recipe - - - -To get started, initialise the Dashboard recipe in the `recipeList`. - - - - -```tsx -import SuperTokens from "supertokens-node"; -import Dashboard from "supertokens-node/recipe/dashboard"; - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // TODO: Initialise other recipes - // highlight-start - Dashboard.init(), - // highlight-end - ], -}); -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/dashboard" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - supertokens.Init(supertokens.TypeInput{ - RecipeList: []supertokens.Recipe{ - // TODO: Initialise other recipes - // highlight-start - dashboard.Init(nil), - // highlight-end - }, - }); -} -``` - - - - -```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import dashboard - -init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore - recipe_list=[ - # TODO: Initialise other recipes - # highlight-start - dashboard.init(), - # highlight-end - ] -) -``` - - - - -## Viewing the dashboard - -:::important -The user management dashboard is served by the backend SDK, you have to use your API domain when trying to visit the dashboard. -::: - -Navigate to `^{form_apiDomain}^{form_apiBasePath}/dashboard` to view the dashboard. - -:::note -If you are using Next.js, upon integrating our backend SDK into your Next.js API folder, the dashboard will be accessible by default at `^{form_apiDomain}/api/auth/dashboard`. For frameworks other than Next.js, it can be accessed at `^{form_apiDomain}/auth/dashboard`. Should you have customized the `apiBasePath` configuration property, simply navigate to `^{form_apiDomain}^{form_apiBasePath}/dashboard` to access the dashboard. -::: - -Dashboard login screen UI - -## Creating dashboard credentials - - - -You can create 3 dashboard users* for free. - -If you need to create additional users: - -- For self hosted users, please [sign up](https://supertokens.com/auth) to generate a license key and follow the instructions sent to you by email. -- For managed service users, you can click on the "enable paid features" button on [our dashboard](https://supertokens.com/dashboard-saas), and follow the steps from there on. - -*: A dashboard user is a user that can log into and view the user management dashboard. These users are independent to the users of your application - - - -When you first setup SuperTokens, there are no credentials created for the dashboard. If you click the "Add a new user" button in the dashboard login screen you can see the command you need to execute in order to create credentials. - -Dashboard signup screen UI - -To create credentials you need to make a request to SuperTokens core. - -- The example above uses the demo core `https://try.supertokens.com`, replace this with the connection uri you pass to the backend SDK when initialising SuperTokens. -- Replace `` with your API key. If you are using a self hosted SuperTokens core there is no API key by default. In that case you can either skip or ignore the `api-key` header. -- Replace `` and `` with the appropriate values. - -:::caution -If using self hosted SuperTokens core, you need to make sure that you add an API key to the core in case it's exposed to the internet. Otherwise anyone will be able to create or modify dashboard users. - -You can add an API key to the core by following the instructions "Auth flow customizations" > "SuperTokens core settings" > "Adding API keys" page. -::: - -## Updating dashboard credentials - -You can update the email or password of existing credentials by using the "Forgot Password" button on the dashboard login page. - -Reset your password screen UI - -To update credentials you need to make a request to SuperTokens core. - -- The example above uses the demo core `https://try.supertokens.com`, replace this with the connection uri you pass to the backend SDK when initialising SuperTokens. -- Replace `` with your API key. If you are using a self hosted SuperTokens core there is no API key by default. In that case you can either skip or ignore the `api-key` header. -- Replace `` and `` with the appropriate values. You can use `newEmail` instead of `newPassword` if you want to update the email - - - - - -## Restricting access to dashboard users - -When using the dashboard recipe you can restrict access to certain features by providing a list of emails to be considered as "admins". When a dashboard user logs in with an email not present in this list, they will only be able to perform read operations and all write operations will result in the backend SDKs failing the request. - -You can provide an array of emails to the backend SDK when initialising the dashboard recipe: - -:::important -- Not providing an admins array will result in all dashboard users being allowed both read and write operations -- Providing an empty array as admins will result in all dashboard users having ONLY read access -::: - - - - -```tsx -import SuperTokens from "supertokens-node"; -import Dashboard from "supertokens-node/recipe/dashboard"; - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // TODO: Initialise other recipes - // highlight-start - Dashboard.init({ - admins: [ - "johndoe@gmail.com", - ], - }), - // highlight-end - ], -}); -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/dashboard" - "github.com/supertokens/supertokens-golang/supertokens" - "github.com/supertokens/supertokens-golang/recipe/dashboard/dashboardmodels" -) - -func main() { - supertokens.Init(supertokens.TypeInput{ - RecipeList: []supertokens.Recipe{ - // TODO: Initialise other recipes - // highlight-start - dashboard.Init(&dashboardmodels.TypeInput{ - Admins: &[]string{ - "johndoe@gmail.com", - }, - }), - // highlight-end - }, - }); -} -``` - - - - -```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import dashboard - -init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore - recipe_list=[ - # TODO: Initialise other recipes - # highlight-start - dashboard.init( - admins=[ - "johndoe@gmail.com", - ], - ), - # highlight-end - ] -) -``` - - - - -## Content Security Policy - - - - -If your backend returns a `Content-Security-Policy` header, you will encounter the following UI displaying the CSP violation details. Follow the instructions provided in this UI to make necessary adjustments to your backend CSP configuration. - -![CSP error handled UI](/img/dashboard/csp-error.png) - - -For example, to address the error message displayed in the above screenshot, you need to modify your `original policy`. In the given example, it appears as follows: - -```text -script-src: - 'self' - 'unsafe-inline' - https://google.com - -img-src: - https://google.com -``` - -To resolve this issue, make the following adjustments: - - -```text -script-src: - 'self' - 'unsafe-inline' - https://google.com - -img-src: - https://google.com - https://cdn.jsdelivr.net/gh/supertokens/ - -``` -Essentially, you need to include the domain listed as the `Blocked URI` in your violated directive block within your original policy. - - - -If you return a `Content-Security-Policy` header in from your backend, you will need to include the following directives for the user management dashboard to work correctly - -```text -script-src: - 'self' - 'unsafe-inline' - https://cdn.jsdelivr.net/gh/supertokens/ -img-src: - https://cdn.jsdelivr.net/gh/supertokens/ - https://purecatamphetamine.github.io/ -``` - - - - -If you return a `Content-Security-Policy` header in from your backend, you will need to include the following directives for the user management dashboard to work correctly - -```text -script-src: - 'self' - 'unsafe-inline' - https://cdn.jsdelivr.net/gh/supertokens/ -img-src: - https://cdn.jsdelivr.net/gh/supertokens/ - https://purecatamphetamine.github.io/ -``` - - - - - \ No newline at end of file diff --git a/v2/thirdpartypasswordless/custom-ui/init/user-management-dashboard/tenant-management/details.mdx b/v2/thirdpartypasswordless/custom-ui/init/user-management-dashboard/tenant-management/details.mdx index 4719a57cf..1397ce110 100644 --- a/v2/thirdpartypasswordless/custom-ui/init/user-management-dashboard/tenant-management/details.mdx +++ b/v2/thirdpartypasswordless/custom-ui/init/user-management-dashboard/tenant-management/details.mdx @@ -4,65 +4,7 @@ title: "Tenant Details" hide_title: true --- - - - +import Redirector from '/src/components/Redirector'; -# Tenant details + -Upon selection or creation of a tenant, you will be presented with the Tenant Details page. The various sections are explained below. - -Tenant details - -## Tenant ID and users - -The first section shows up the tenant ID and the number of users in that tenant. Clicking on `See Users` takes you to the [user management page](/docs/userdashboard/users-listing-and-details) where the users for the selected tenant can be viewed and managed. - -Tenant users - - -## Enabled Login Methods - -This section displays the various login methods available for the tenant. By enabling these toggles, you can make the corresponding login methods accessible to the users within the tenant. - -Appropriate recipes must be enabled to be able to turn on the login methods. For example, - -- to be able to turn on `emailpassword`, EmailPassword recipe must be initialised in the backend. -- to be able to turn on `OTP Phone`, Passwordless recipe must be initialised with flowType `USER_INPUT_CODE` and contactMethod `PHONE` - -:::info - -If you are using our Auth React SDK, make sure to enable [usesDynamicLoginMethods](/docs/passwordless/common-customizations/multi-tenancy/common-domain-login#step-3-tell-supertokens-about-the-saved-tenantid-from-the-previous-step) so that the frontend can automatically show the login methods based on the selection here. - -::: - -Login Methods - -## Secondary factors - -This section displays the various secondary factors available for the tenant. By enabling these toggles, the corresponding factor will be enabled for all users of the tenant. Refer [Multifactor Authentication docs](/docs/mfa/introduction) for more information. - -[MultiFactorAuth](/docs/mfa/backend-setup) recipe must be initialised to be able to enable Secondary Factors. - -Also, appropriate recipes must be initialised in the backend SDK to be able to use a secondary factor. For example, - -- to be able to turn on TOTP, TOTP recipe must be initialised in the backend. -- to be able to turn on `OTP Phone`, Passwordless recipe must be initialised with flowType `USER_INPUT_CODE` and contactMethod `PHONE` - -Secondary Factors - -## Core config - -Core Config - -This section shows the current config values in core for the tenant. You can edit some of these settings by clicking the `pencil` icon next to the property. - -Edit Core Config - -:::caution - -Some of the config values may not be editable since they are being inherited from the App. If using Supertokens managed hosting, they can be modified in the SaaS Dashboard. Else, if you are self-hosting the SuperTokens core, they will have to be edited via Docker env variables or the config.yaml file. - -::: - - diff --git a/v2/thirdpartypasswordless/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx b/v2/thirdpartypasswordless/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx index 32c5447bd..6ae231e40 100644 --- a/v2/thirdpartypasswordless/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx +++ b/v2/thirdpartypasswordless/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx @@ -4,29 +4,7 @@ title: "Overview" hide_title: true --- - - - +import Redirector from '/src/components/Redirector'; -# Tenant management overview + -You can now manage [tenants, login methods and third party providers](/docs/multitenancy/introduction) and [Multi factor authentication](/docs/mfa/introduction) of your app from the tenant management dashboard. - -Once the dashboard recipe is initialised, the tenant management should be available without requiring any additional steps. - -:::caution - -Currently, this is only available with our Node and Python SDKs. - -::: - -Tenant Management Landing - - -## Creating a new tenant - -Clicking on `Add Tenant` will prompt you to enter the tenant id. Once the tenant id is entered, click on `Create Now` to create the tenant. You will then be taken to the Tenant Details page where you can further manage the newly created tenant. - -Create Tenant - - diff --git a/v2/thirdpartypasswordless/custom-ui/init/user-management-dashboard/users-listing-and-details.mdx b/v2/thirdpartypasswordless/custom-ui/init/user-management-dashboard/users-listing-and-details.mdx index ff5945ed2..7be11467c 100644 --- a/v2/thirdpartypasswordless/custom-ui/init/user-management-dashboard/users-listing-and-details.mdx +++ b/v2/thirdpartypasswordless/custom-ui/init/user-management-dashboard/users-listing-and-details.mdx @@ -4,38 +4,7 @@ title: "Viewing user list and details" hide_title: true --- - - - +import Redirector from '/src/components/Redirector'; -# Viewing user list and details + -With the user management dashboard you can view the list of users and their details. You can also perform different operations on theses users as mentioned below. - -## Viewing users list - -If you have just created your app, you may not have any users to show on the dashboard. - -Empty dashboard screen UI - -Navigate to the your frontend app and create a user (via the sign up flow). On creation, if you head back to the dashboard and refresh the page, you will see that user: - -One user in dashboard screen UI - -## User details page - -When you select a user you can view detailed information about the user such as email, phone number, user metadata etc. - -User details page screen UI part one - -User details page screen UI part two - -You can edit user information and perform actions such as resetting a user's password or revoke sessions for a user. - -Change password modal UI - -:::important Note -Some features such as user metadata and email verification have to be enabled in your backend before you can use them in the user management dashboard -::: - - diff --git a/v2/thirdpartypasswordless/custom-ui/login-magic-link.mdx b/v2/thirdpartypasswordless/custom-ui/login-magic-link.mdx index 15116c23c..f8c3b1f63 100644 --- a/v2/thirdpartypasswordless/custom-ui/login-magic-link.mdx +++ b/v2/thirdpartypasswordless/custom-ui/login-magic-link.mdx @@ -4,654 +4,9 @@ title: Magic link login hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" -import TabItem from '@theme/TabItem'; -import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" -import AppInfoForm from "/src/components/appInfoForm" -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import CustomAdmonition from "/src/components/customAdmonition" -# Magic link login + -There are three parts to Magic link based login: -- Creating and sending the magic link to the user. -- Allowing the user to resend a (new) magic link if they want. -- Consuming the link (when clicked) to login the user. -:::note -The same flow applies during sign up and sign in. If the user is signing up, the `createdNewUser` boolean on the frontend and backend will be `true` (as the result of the consume code API call). -::: - -## Step 1: Sending the Magic link -SuperTokens allows you to send a magic link to a user's email or phone number. You have already configured this setting on the backend SDK `init` function call in "Initialisation" section. - -Start by making a form which asks the user for their email or phone, and then call the following API to create and send them a magic link - - - - - - - -```tsx -import { createCode } from "supertokens-web-js/recipe/passwordless"; - -async function sendMagicLink(email: string) { - try { - let response = await createCode({ - email - }); - /** - * For phone number, use this: - - let response = await createCode({ - phoneNumber: "+1234567890" - }); - - */ - - if (response.status === "SIGN_IN_UP_NOT_ALLOWED") { - // the reason string is a user friendly message - // about what went wrong. It can also contain a support code which users - // can tell you so you know why their sign in / up was not allowed. - window.alert(response.reason) - } else { - // Magic link sent successfully. - window.alert("Please check your email for the magic link"); - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you, - // or if the input email / phone number is not valid. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - -```tsx -import supertokensPasswordless from "supertokens-web-js-script/recipe/passwordless"; -async function sendMagicLink(email: string) { - try { - let response = await supertokensPasswordless.createCode({ - email - }); - /** - * For phone number, use this: - - let response = await supertokens^{recipeNameCapitalLetters}.createCode({ - phoneNumber: "+1234567890" - }); - - */ - - if (response.status === "SIGN_IN_UP_NOT_ALLOWED") { - // the reason string is a user friendly message - // about what went wrong. It can also contain a support code which users - // can tell you so you know why their sign in / up was not allowed. - window.alert(response.reason) - } else { - // Magic link sent successfully. - window.alert("Please check your email for the magic link"); - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you, - // or if the input email / phone number is not valid. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - - - - - - - -For email based login - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup/code' \ ---header 'Content-Type: application/json; charset=utf-8' \ ---data-raw '{ - "email": "johndoe@gmail.com" -}' -``` - -For phone number based login -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup/code' \ ---header 'Content-Type: application/json; charset=utf-8' \ ---data-raw '{ - "phoneNumber": "+1234567890" -}' -``` - - - -The response body from the API call has a `status` property in it: -- `status: "OK"`: This means that the magic link was successfully sent. -- `status: "GENERAL_ERROR"`: This is possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend, or if the input email or password failed the backend validation logic. -- `status: "SIGN_IN_UP_NOT_ALLOWED"`: This can happen during automatic account linking or during MFA. The `reason` prop that's in the response body contains a support code using which you can see why the sign in / up was not allowed. - -The response from the API call is the following object (in case of `status: "OK"`): -```json -{ - status: "OK"; - deviceId: string; - preAuthSessionId: string; - flowType: "USER_INPUT_CODE" | "MAGIC_LINK" | "USER_INPUT_CODE_AND_MAGIC_LINK"; - fetchResponse: Response; // raw fetch response from the API call -} -``` - -You want to save the `deviceId` and `preAuthSessionId` on the frontend storage. These will be useful to: -- Resend a new magic link. -- Detect if the user has already sent a magic link before or if this is an entirely new login attempt. This distinction can be important if you have different UI for these two states. For example, if this info already exists, you do not want to show the user an input box to enter their email / phone, and instead want to show them the resend link button. - - - - - -### Changing the magic link URL, or deep linking it to your app -By default, the magic link will point to the `websiteDomain` that is configured on the backend, on the `/auth/verify` route (where `/auth` is the default value of `websiteBasePath`). - -If you want to change this to a different path, a different domain, or deep link it to your mobile / desktop app, then you can do so on the backend in the following way: - - - - -```tsx -import SuperTokens from "supertokens-node"; -import Passwordless from "supertokens-node/recipe/passwordless"; -import Session from "supertokens-node/recipe/session"; - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "..." - }, - recipeList: [ - Passwordless.init({ - contactMethod: "EMAIL", // This example will work with any contactMethod - // This example works with the "USER_INPUT_CODE_AND_MAGIC_LINK" and "MAGIC_LINK" flows. - flowType: "USER_INPUT_CODE_AND_MAGIC_LINK", - - // highlight-start - emailDelivery: { - // highlight-start - override: (originalImplementation) => { - return { - ...originalImplementation, - sendEmail: async function (input) { - return originalImplementation.sendEmail({ - ...input, - urlWithLinkCode: input.urlWithLinkCode?.replace( - // This is: `${websiteDomain}${websiteBasePath}/verify` - "http://localhost:3000/auth/verify", - "http://your.domain.com/your/path" - ) - }) - } - } - } - } - // highlight-end - }), - Session.init({ /* ... */ }) - ] -}); -``` - - - -```go -import ( - "strings" - - "github.com/supertokens/supertokens-golang/ingredients/emaildelivery" - "github.com/supertokens/supertokens-golang/recipe/passwordless" - "github.com/supertokens/supertokens-golang/recipe/passwordless/plessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - supertokens.Init(supertokens.TypeInput{ - RecipeList: []supertokens.Recipe{ - passwordless.Init(plessmodels.TypeInput{ - EmailDelivery: &emaildelivery.TypeInput{ - // highlight-start - Override: func(originalImplementation emaildelivery.EmailDeliveryInterface) emaildelivery.EmailDeliveryInterface { - ogSendEmail := *originalImplementation.SendEmail - (*originalImplementation.SendEmail) = func(input emaildelivery.EmailType, userContext supertokens.UserContext) error { - // By default: `${websiteDomain}/${websiteBasePath}/verify` - newUrl := strings.Replace( - *input.PasswordlessLogin.UrlWithLinkCode, - "http://localhost:3000/auth/verify", - "http://localhost:3000/custom/path", - 1, - ) - input.PasswordlessLogin.UrlWithLinkCode = &newUrl - return ogSendEmail(input, userContext) - } - return originalImplementation - }, - // highlight-end - }, - }), - }, - }) -} -``` - - - - -```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe.passwordless.types import EmailDeliveryOverrideInput, EmailTemplateVars -from supertokens_python.recipe import passwordless -from typing import Dict, Any -from supertokens_python.ingredients.emaildelivery.types import EmailDeliveryConfig - -def custom_email_deliver(original_implementation: EmailDeliveryOverrideInput) -> EmailDeliveryOverrideInput: - original_send_email = original_implementation.send_email - - # highlight-start - async def send_email(template_vars: EmailTemplateVars, user_context: Dict[str, Any]) -> None: - assert template_vars.url_with_link_code is not None - # By default: `${websiteDomain}/${websiteBasePath}/verify` - template_vars.url_with_link_code = template_vars.url_with_link_code.replace( - "http://localhost:3000/auth/verify", "http://localhost:3000/custom/path") - return await original_send_email(template_vars, user_context) - # highlight-end - - original_implementation.send_email = send_email - return original_implementation - -init( - app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore - recipe_list=[ - passwordless.init( - contact_config="", # type: ignore # typecheck-only, removed from output - flow_type="USER_INPUT_CODE", # typecheck-only, removed from output - email_delivery=EmailDeliveryConfig(override=custom_email_deliver) - ) - ] -) -``` - - - - - - -For a multi tenant setup, the input to the sendEmail function will also contain the tenantId. You can use this to determine the correct value to set for the websiteDomain in the generated link. - - - -## Step 2: Resending a (new) Magic link -After sending the initial magic link to the user, you may want to display a resend button to them. When the user clicks on this button, you should call the following API - - - - - - - -```tsx -import { resendCode, clearLoginAttemptInfo } from "supertokens-web-js/recipe/passwordless"; - -async function resendMagicLink() { - try { - let response = await resendCode(); - - if (response.status === "RESTART_FLOW_ERROR") { - // this can happen if the user has already successfully logged in into - // another device whilst also trying to login to this one. - - // we clear the login attempt info that was added when the createCode function - // was called - so that if the user does a page reload, they will now see the - // enter email / phone UI again. - await clearLoginAttemptInfo(); - window.alert("Login failed. Please try again"); - window.location.assign("/auth") - } else { - // Magic link resent successfully. - window.alert("Please check your email for the magic link"); - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - -```tsx -import supertokensPasswordless from "supertokens-web-js-script/recipe/passwordless"; -async function resendMagicLink() { - try { - let response = await supertokensPasswordless.resendCode(); - - if (response.status === "RESTART_FLOW_ERROR") { - // this can happen if the user has already successfully logged in into - // another device whilst also trying to login to this one. - - // we clear the login attempt info that was added when the createCode function - // was called - so that if the user does a page reload, they will now see the - // enter email / phone UI again. - await supertokensPasswordless.clearLoginAttemptInfo(); - window.alert("Login failed. Please try again"); - window.location.assign("/auth") - } else { - // Magic link resent successfully. - window.alert("Please check your email for the magic link"); - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - - - - - - - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup/code/resend' \ ---header 'Content-Type: application/json; charset=utf-8' \ ---data-raw '{ - "deviceId": "...", - "preAuthSessionId": "...." -}' -``` - - - -The response body from the API call has a `status` property in it: -- `status: "OK"`: This means that the magic link was successfully sent. -- `status: "RESTART_FLOW_ERROR"`: This can happen if the user has already successfully logged in into another device whilst also trying to login to this one. You want to take the user back to the login screen where they can enter their email / phone number again. Be sure to remove the stored `deviceId` and `preAuthSessionId` from the frontend storage. -- `status: "GENERAL_ERROR"`: This is possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. - - - - - -### How to detect if the user is in (Step 1) or in (Step 2) state? -If you are building the UI for (Step 1) and (Step 2) on the same page, and if the user refreshes the page, you need a way to know which UI to show - the enter email / phone number form; or the resend magic link form. - - - - - - - -```tsx -import { getLoginAttemptInfo } from "supertokens-web-js/recipe/passwordless"; - -async function hasInitialMagicLinkBeenSent() { - return await getLoginAttemptInfo() !== undefined; -} -``` - - - - -```tsx -import supertokensPasswordless from "supertokens-web-js-script/recipe/passwordless"; -async function hasInitialMagicLinkBeenSent() { - return await supertokensPasswordless.getLoginAttemptInfo() !== undefined; -} -``` - - - - -If `hasInitialMagicLinkBeenSent` returns `true`, it means that the user has already sent the initial magic link to themselves, and you can show the resend link UI (Step 2). Else show a form asking them to enter their email / phone number (Step 1). - - - - - -Since you save the `preAuthSessionId` and `deviceId` after the initial magic link is sent, you can know if the user is in (Step 1) vs (Step 2) by simply checking if these tokens are stored on the device. - -If they aren't, you should follow (Step 1), else follow (Step 2). - -:::important -You need to clear these tokens if the user navigates away from the (Step 2) page, or if you get a `RESTART_FLOW_ERROR` at any point in time from an API call, or if the user has successfully logged in. -::: - - - - - -## Step 3: Consuming the Magic link -This section talks about what needs to be done when the user clicks on the Magic link. There are two situations here: -- The user clicks the Magic link on the same browser & device as the one they had started the flow on. -- The user clicks the link on a different browser or device. - -In order to detect which it is, you can do the following: - - - - - - - -```tsx -import { getLoginAttemptInfo } from "supertokens-web-js/recipe/passwordless"; - -async function isThisSameBrowserAndDevice() { - return await getLoginAttemptInfo() !== undefined; -} -``` - - - - -```tsx -import supertokensPasswordless from "supertokens-web-js-script/recipe/passwordless"; -async function isThisSameBrowserAndDevice() { - return await supertokensPasswordless.getLoginAttemptInfo() !== undefined; -} -``` - - - - - - - - -Since you save the `preAuthSessionId` and `deviceId`, you can check if they exist on the app. If they do, then it's the same device that the user has opened the link on, else it's a different device. - - - - - -### If on the same device & browser - - - - -On page load, you can consume the magic link by calling the following function - - - - -```tsx -import { consumeCode, clearLoginAttemptInfo } from "supertokens-web-js/recipe/passwordless"; - -async function handleMagicLinkClicked() { - try { - let response = await consumeCode(); - - if (response.status === "OK") { - // we clear the login attempt info that was added when the createCode function - // was called since the login was successful. - await clearLoginAttemptInfo(); - if (response.createdNewRecipeUser && response.user.loginMethods.length === 1) { - // user sign up success - } else { - // user sign in success - } - window.location.assign("/home") - } else { - // this can happen if the magic link has expired or is invalid - // or if it was denied due to security reasons in case of automatic account linking - - // we clear the login attempt info that was added when the createCode function - // was called - so that if the user does a page reload, they will now see the - // enter email / phone UI again. - await clearLoginAttemptInfo(); - window.alert("Login failed. Please try again"); - window.location.assign("/auth") - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - -```tsx -import supertokensPasswordless from "supertokens-web-js-script/recipe/passwordless"; -async function handleMagicLinkClicked() { - try { - let response = await supertokensPasswordless.consumeCode(); - - if (response.status === "OK") { - // we clear the login attempt info that was added when the createCode function - // was called since the login was successful. - await supertokensPasswordless.clearLoginAttemptInfo(); - if (response.createdNewRecipeUser && response.user.loginMethods.length === 1) { - // user sign up success - } else { - // user sign in success - } - window.location.assign("/home") - } else { - // this can happen if the magic link has expired or is invalid - // or if it was denied due to security reasons in case of automatic account linking - - // we clear the login attempt info that was added when the createCode function - // was called - so that if the user does a page reload, they will now see the - // enter email / phone UI again. - await supertokensPasswordless.clearLoginAttemptInfo(); - window.alert("Login failed. Please try again"); - window.location.assign("/auth") - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - - - - - -You need to extract the `linkCode` and `preAuthSessionId` from the Magic link. For example, if the Magic link is - -```text -https://example.com/auth/verify?preAuthSessionId=PyIwyA6VjdjNF5ggMV960rs3QXupRP2PEg2KcN5oi8s=#s4hxpBPnRC3xwBsCkFU228lh_CWe5HUBMRPowajsrgs= -``` - -Then the `preAuthSessionId` is the value of the query param `preAuthSessionId` (`PyIwyA6VjdjNF5ggMV960rs3QXupRP2PEg2KcN5oi8s=` in the example), and the `linkCode` is the part after the `#` (`s4hxpBPnRC3xwBsCkFU228lh_CWe5HUBMRPowajsrgs=` in our example). - -We can then use these to call the consume API - - - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup/code/consume' \ ---header 'Content-Type: application/json; charset=utf-8' \ ---data-raw '{ - "linkCode": "s4hxpBPnRC3xwBsCkFU228lh_CWe5HUBMRPowajsrgs=", - "preAuthSessionId": "PyIwyA6VjdjNF5ggMV960rs3QXupRP2PEg2KcN5oi8s=" -}' -``` - - - - - -For a multi tenancy setup, the `` value can be fetched from `tenantId` query parameter from the magic link. If it's not there in the link, you can use the value `"public"` (which is the default tenant). - - - -The response body from the API call has a `status` property in it: -- `status: "OK"`: User sign in / up was successful. The response also contains more information about the user, for example their user ID, and if it was a new user or existing user. -- `status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" | "RESTART_FLOW_ERROR"`: These responses indicate that the Magic link was invalid or expired. -- `status: "GENERAL_ERROR"`: This is possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. -- `status: "SIGN_IN_UP_NOT_ALLOWED"`: This can happen during automatic account linking or during MFA. The `reason` prop that's in the response body contains a support code using which you can see why the sign in / up was not allowed. - - - - - -:::note -On success, the backend will send back session tokens as part of the response headers which will be automatically handled by our frontend SDK for you. -::: - -### If on a different device or browser -In this case, you want to show some UI that requires a user interaction before consuming the magic link. This is to protect against email clients opening the magic link on their servers and consuming the link. For example, you could show a button with text like - "Click here to login into this device". - -On click, you can consume the magic link to log the user into that device. Follow the instructions in the above section to know which function / API to call. - -## See also -- [Post sign in / sign up action](../common-customizations/handling-signinup-success) -- [Changing the magic link lifetime](../common-customizations/change-code-lifetime) -- Changing [the content of](../email-delivery/about) or how [emails and / or SMS are sent](../email-delivery/about) -- [Generating magic links without user action](../common-customizations/generating-magic-link-manually) -- [Changing email or phone number validation logic](../common-customizations/sign-in-and-up/change-email-phone-validation) -- [Customising user ID format](../common-customizations/userid-format) \ No newline at end of file diff --git a/v2/thirdpartypasswordless/custom-ui/login-otp.mdx b/v2/thirdpartypasswordless/custom-ui/login-otp.mdx index 93e712b1a..2e86f70c1 100644 --- a/v2/thirdpartypasswordless/custom-ui/login-otp.mdx +++ b/v2/thirdpartypasswordless/custom-ui/login-otp.mdx @@ -4,471 +4,9 @@ title: OTP login hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" -import TabItem from '@theme/TabItem'; -import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" -import AppInfoForm from "/src/components/appInfoForm" -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -# OTP login + -There are three parts to OTP login: -- Creating and sending the OTP to the user. -- Allowing the user to resend a (new) OTP if they want. -- Validating the user's input OTP to login the user. -:::note -The same flow applies during sign up and sign in. If the user is signing up, the `createdNewUser` boolean on the frontend and backend will be `true` (as the result of the consume code API call). -::: - -## Step 1: Creating and sending the OTP -SuperTokens allows you to send an OTP to a user's email or phone number. You have already configured this setting on the backend SDK `init` function call in "Initialisation" section. - -Start by making a form which asks the user for their email or phone, and then call the following API to create and send them an OTP. - - - - - - - -```tsx -import { createCode } from "supertokens-web-js/recipe/passwordless"; - -async function sendOTP(email: string) { - try { - let response = await createCode({ - email - }); - /** - * For phone number, use this: - - let response = await createPasswordlessCode({ - phoneNumber: "+1234567890" - }); - - */ - - if (response.status === "SIGN_IN_UP_NOT_ALLOWED") { - // the reason string is a user friendly message - // about what went wrong. It can also contain a support code which users - // can tell you so you know why their sign in / up was not allowed. - window.alert(response.reason) - } else { - // OTP sent successfully. - window.alert("Please check your email for an OTP"); - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you, - // or if the input email / phone number is not valid. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - -```tsx -import supertokensPasswordless from "supertokens-web-js-script/recipe/passwordless"; -async function sendOTP(email: string) { - try { - let response = await supertokensPasswordless.createCode({ - email - }); - /** - * For phone number, use this: - - let response = await supertokens^{recipeNameCapitalLetters}.createCode({ - phoneNumber: "+1234567890" - }); - - */ - - if (response.status === "SIGN_IN_UP_NOT_ALLOWED") { - // the reason string is a user friendly message - // about what went wrong. It can also contain a support code which users - // can tell you so you know why their sign in / up was not allowed. - window.alert(response.reason) - } else { - // OTP sent successfully. - window.alert("Please check your email for an OTP"); - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you, - // or if the input email / phone number is not valid. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - - - - - - - -For email based login - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup/code' \ ---header 'Content-Type: application/json; charset=utf-8' \ ---data-raw '{ - "email": "johndoe@gmail.com" -}' -``` - -For phone number based login -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup/code' \ ---header 'Content-Type: application/json; charset=utf-8' \ ---data-raw '{ - "phoneNumber": "+1234567890" -}' -``` - - - -The response body from the API call has a `status` property in it: -- `status: "OK"`: This means that the OTP was successfully sent. -- `status: "GENERAL_ERROR"`: This is possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend, or if the input email or password failed the backend validation logic. -- `status: "SIGN_IN_UP_NOT_ALLOWED"`: This can happen during automatic account linking or during MFA. The `reason` prop that's in the response body contains a support code using which you can see why the sign in / up was not allowed. - -The response from the API call is the following object (in case of `status: "OK"`): -```json -{ - status: "OK"; - deviceId: string; - preAuthSessionId: string; - flowType: "USER_INPUT_CODE" | "MAGIC_LINK" | "USER_INPUT_CODE_AND_MAGIC_LINK"; - fetchResponse: Response; // raw fetch response from the API call -} -``` - -You want to save the `deviceId` and `preAuthSessionId` on the frontend storage. These will be useful to: -- Resend a new OTP. -- Detect if the user has already sent an OTP before or if this is an entirely new login attempt. This distinction can be important if you have different UI for these two states. For example, if this info already exists, you do not want to show the user an input box to enter their email / phone, and instead want to show them the enter OTP form with a resend button. -- Verify the user's input OTP. - - - - - -## Step 2: Resending a (new) OTP -After sending the initial OTP to the user, you may want to display a resend button to them. When the user clicks on this button, you should call the following API - - - - - - - -```tsx -import { resendCode, clearLoginAttemptInfo } from "supertokens-web-js/recipe/passwordless"; - -async function resendOTP() { - try { - let response = await resendCode(); - - if (response.status === "RESTART_FLOW_ERROR") { - // this can happen if the user has already successfully logged in into - // another device whilst also trying to login to this one. - - // we clear the login attempt info that was added when the createCode function - // was called - so that if the user does a page reload, they will now see the - // enter email / phone UI again. - await clearLoginAttemptInfo(); - window.alert("Login failed. Please try again"); - window.location.assign("/auth") - } else { - // OTP resent successfully. - window.alert("Please check your email for the OTP"); - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - -```tsx -import supertokensPasswordless from "supertokens-web-js-script/recipe/passwordless"; -async function resendOTP() { - try { - let response = await supertokensPasswordless.resendCode(); - - if (response.status === "RESTART_FLOW_ERROR") { - // this can happen if the user has already successfully logged in into - // another device whilst also trying to login to this one. - - // we clear the login attempt info that was added when the createCode function - // was called - so that if the user does a page reload, they will now see the - // enter email / phone UI again. - await supertokensPasswordless.clearLoginAttemptInfo(); - window.alert("Login failed. Please try again"); - window.location.assign("/auth") - } else { - // OTP resent successfully. - window.alert("Please check your email for the OTP"); - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - - - - - - - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup/code/resend' \ ---header 'Content-Type: application/json; charset=utf-8' \ ---data-raw '{ - "deviceId": "...", - "preAuthSessionId": "...." -}' -``` - - - -The response body from the API call has a `status` property in it: -- `status: "OK"`: This means that the OTP was successfully sent. -- `status: "RESTART_FLOW_ERROR"`: This can happen if the user has already successfully logged in into another device whilst also trying to login to this one. You want to take the user back to the login screen where they can enter their email / phone number again. Be sure to remove the stored `deviceId` and `preAuthSessionId` from the frontend storage. -- `status: "GENERAL_ERROR"`: This is possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. - - - - - -### How to detect if the user is in (Step 1) or in (Step 2) state? -If you are building the UI for (Step 1) and (Step 2) on the same page, and if the user refreshes the page, you need a way to know which UI to show - the enter email / phone number form; or enter OTP + resend OTP form. - - - - - - - -```tsx -import { getLoginAttemptInfo } from "supertokens-web-js/recipe/passwordless"; - -async function hasInitialOTPBeenSent() { - return await getLoginAttemptInfo() !== undefined; -} -``` - - - - -```tsx -import supertokensPasswordless from "supertokens-web-js-script/recipe/passwordless"; -async function hasInitialOTPBeenSent() { - return await supertokensPasswordless.getLoginAttemptInfo() !== undefined; -} -``` - - - - -If `hasInitialOTPBeenSent` returns `true`, it means that the user has already sent the initial OTP to themselves, and you can show the enter OTP form + resend OTP button (Step 2). Else show a form asking them to enter their email / phone number (Step 1). - - - - - -Since you save the `preAuthSessionId` and `deviceId` after the initial OTP is sent, you can know if the user is in (Step 1) vs (Step 2) by simply checking if these tokens are stored on the device. - -If they aren't, you should follow (Step 1), else follow (Step 2). - -:::important -You need to clear these tokens if the user navigates away from the (Step 2) page, or if you get a `RESTART_FLOW_ERROR` at any point in time from an API call, or if the user has successfully logged in. -::: - - - - - -## Step 3: Verifying the input OTP -When the user enters an OTP, you want to call the following API to verify it - - - - - - - -```tsx -import { consumeCode, clearLoginAttemptInfo } from "supertokens-web-js/recipe/passwordless"; - -async function handleOTPInput(otp: string) { - try { - let response = await consumeCode({ - userInputCode: otp - }); - - if (response.status === "OK") { - // we clear the login attempt info that was added when the createCode function - // was called since the login was successful. - await clearLoginAttemptInfo(); - if (response.createdNewRecipeUser && response.user.loginMethods.length === 1) { - // user sign up success - } else { - // user sign in success - } - window.location.assign("/home") - } else if (response.status === "INCORRECT_USER_INPUT_CODE_ERROR") { - // the user entered an invalid OTP - window.alert("Wrong OTP! Please try again. Number of attempts left: " + (response.maximumCodeInputAttempts - response.failedCodeInputAttemptCount)); - } else if (response.status === "EXPIRED_USER_INPUT_CODE_ERROR") { - // it can come here if the entered OTP was correct, but has expired because - // it was generated too long ago. - window.alert("Old OTP entered. Please regenerate a new one and try again"); - } else { - // this can happen if the user tried an incorrect OTP too many times. - // or if it was denied due to security reasons in case of automatic account linking - - // we clear the login attempt info that was added when the createCode function - // was called - so that if the user does a page reload, they will now see the - // enter email / phone UI again. - await clearLoginAttemptInfo(); - window.alert("Login failed. Please try again"); - window.location.assign("/auth") - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - -```tsx -import supertokensPasswordless from "supertokens-web-js-script/recipe/passwordless"; -async function handleOTPInput(otp: string) { - try { - let response = await supertokensPasswordless.consumeCode({ - userInputCode: otp - }); - - if (response.status === "OK") { - // we clear the login attempt info that was added when the createCode function - // was called since the login was successful. - await supertokensPasswordless.clearLoginAttemptInfo(); - if (response.createdNewRecipeUser && response.user.loginMethods.length === 1) { - // user sign up success - } else { - // user sign in success - } - window.location.assign("/home") - } else if (response.status === "INCORRECT_USER_INPUT_CODE_ERROR") { - // the user entered an invalid OTP - window.alert("Wrong OTP! Please try again. Number of attempts left: " + (response.maximumCodeInputAttempts - response.failedCodeInputAttemptCount)); - } else if (response.status === "EXPIRED_USER_INPUT_CODE_ERROR") { - // it can come here if the entered OTP was correct, but has expired because - // it was generated too long ago. - window.alert("Old OTP entered. Please regenerate a new one and try again"); - } else { - // this can happen if the user tried an incorrect OTP too many times. - // or if it was denied due to security reasons in case of automatic account linking - - // we clear the login attempt info that was added when the createCode function - // was called - so that if the user does a page reload, they will now see the - // enter email / phone UI again. - await supertokensPasswordless.clearLoginAttemptInfo(); - window.alert("Login failed. Please try again"); - window.location.assign("/auth") - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - - - - - - - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup/code/consume' \ ---header 'Content-Type: application/json; charset=utf-8' \ ---data-raw '{ - "deviceId": "...", - "preAuthSessionId": "...", - "userInputCode": "" -}' -``` - - - -The response body from the API call has a `status` property in it: -- `status: "OK"`: User sign in / up was successful. The response also contains more information about the user, for example their user ID, and if it was a new user or existing user. -- `status: "INCORRECT_USER_INPUT_CODE_ERROR"`: The entered OTP is invalid. The response also contains information about the maximum number of retries and the number of failed attempts so far. -- `status: "EXPIRED_USER_INPUT_CODE_ERROR"`: The entered OTP is too old. You should ask the user to resend a new OTP and try again. -- `status: "RESTART_FLOW_ERROR"`: These responses that the user tried invalid OTPs too many times. -- `status: "GENERAL_ERROR"`: This is possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. -- `status: "SIGN_IN_UP_NOT_ALLOWED"`: This can happen during automatic account linking or during MFA. The `reason` prop that's in the response body contains a support code using which you can see why the sign in / up was not allowed. - - - - - -:::note -On success, the backend will send back session tokens as part of the response headers which will be automatically handled by our frontend SDK for you. -::: - -## See also -- [Post sign in / sign up action](../common-customizations/handling-signinup-success) -- [Changing the OTP's lifetime](../common-customizations/change-code-lifetime) -- [Changing OTP format](../common-customizations/sign-in-and-up/change-otp-format) -- [Changing the number of max retries for an OTP](../common-customizations/sign-in-and-up/change-maximum-retries) -- Changing [the content of](../email-delivery/about) or how [emails and / or SMS are sent](../email-delivery/about) -- [Changing email or phone number validation logic](../common-customizations/sign-in-and-up/change-email-phone-validation) -- [Customising user ID format](../common-customizations/userid-format) \ No newline at end of file diff --git a/v2/thirdpartypasswordless/custom-ui/multitenant-login.mdx b/v2/thirdpartypasswordless/custom-ui/multitenant-login.mdx index 5505be5f8..bf2091f6c 100644 --- a/v2/thirdpartypasswordless/custom-ui/multitenant-login.mdx +++ b/v2/thirdpartypasswordless/custom-ui/multitenant-login.mdx @@ -4,459 +4,8 @@ title: "Multitenant and Enterprise SSO login" hide_title: true --- -import AppInfoForm from "/src/components/appInfoForm" -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import PythonSyncAsyncSubTabs from "/src/components/tabs/PythonSyncAsyncSubTabs"; -import CoreVersionSubTabs from "/src/components/tabs/CoreVersionSubTabs"; -import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs"; -import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" -import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" -import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" +import Redirector from '/src/components/Redirector'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import MultiTenancyPaidBanner from '../../community/reusableMD/multitenancy/MultiTenancyPaidBanner.mdx' -import CoreInjector from "/src/components/coreInjector" - - - + - - -# Multitenant and Enterprise SSO login - -Multitenant login is a feature that lets you customize the login experience for each of your customers. For example, a customer `customer1` hosted on `customer1.yourdomain.com` can have login with `Active Directory` and `Google`, and another customer `customer2` hosted on `customer2.yourdomain.com` can have login with `Okta`, `Facebook` and magic link based login. - -This is also the page that you should see if you want to implement sign in with: -- Okta (`thirdPartyId: "okta"`) -- SAML (`thirdPartyId: "boxy-saml"`) -- Active Directory (`thirdPartyId: "active-directory"`) -- Google Workspaces (`thirdPartyId: "google-workspaces"`) -- GitLab (`thirdPartyId: "gitlab"`) -- Bitbucket (`thirdPartyId: "bitbucket"`) -- Or any other workforce IdP - - - - - - - -## Step 1: Create and configure a new tenant in SuperTokens core - -Each tenant can be configured with a unique `tenantId`, and the list of third party connections (Active Directory, Google etc..) that should be allowed for them. - -You can create a new tenant using our backend SDKs or via a `cURL` command to the core. - - - - - - - - - - - -```tsx -import Multitenancy from "supertokens-node/recipe/multitenancy"; -import { FactorIds } from "supertokens-node/recipe/multifactorauth"; - -async function createNewTenant() { - - let resp = await Multitenancy.createOrUpdateTenant("customer1", { - firstFactors: [FactorIds.OTP_PHONE, FactorIds.OTP_EMAIL, FactorIds.LINK_PHONE, FactorIds.LINK_EMAIL, FactorIds.THIRDPARTY] - }); - - if (resp.createdNew) { - // new tenant was created - } else { - // existing tenant's config was modified. - } -} -``` - -In the example above, we have added all factor IDs related to the passwordless recipe. However, you can choose to use a subset of them. The factor IDs are described below: -- ThirdParty: `FactorIds.THIRDPARTY` -- Passwordless: - - With email OTP: `FactorIds.OTP_EMAIL` - - With SMS OTP: `FactorIds.OTP_PHONE` - - With email magic link: `FactorIds.LINK_EMAIL` - - With SMS magic link: `FactorIds.LINK_PHONE` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/multitenancy" - "github.com/supertokens/supertokens-golang/recipe/multitenancy/multitenancymodels" -) - -func main() { - tenantId := "customer1" - thirdPartyEnabled := true - passwordlessEnabled := true - - // highlight-start - resp, err := multitenancy.CreateOrUpdateTenant(tenantId, multitenancymodels.TenantConfig{ - ThirdPartyEnabled: &thirdPartyEnabled, - PasswordlessEnabled: &passwordlessEnabled, - }) - // highlight-end - - if err != nil { - // handle error - } - if resp.OK.CreatedNew { - // new tenant was created - } else { - // existing tenant's config was modified. - } -} -``` - - - - - - - -```python -from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate -from supertokens_python.recipe.multifactorauth.types import FactorIds - - -async def some_func(): - response = await create_or_update_tenant( - "customer1", - TenantConfigCreateOrUpdate( - first_factors=[ - FactorIds.OTP_PHONE, - FactorIds.OTP_EMAIL, - FactorIds.LINK_PHONE, - FactorIds.LINK_EMAIL, - FactorIds.THIRDPARTY, - ] - ), - ) - - if response.status != "OK": - print("Handle error") - elif response.created_new: - print("new tenant was created") - else: - print("existing tenant's config was modified.") -``` - - - - - -```python -from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate -from supertokens_python.recipe.multifactorauth.types import FactorIds - - -def some_func(): - response = create_or_update_tenant( - "customer1", - TenantConfigCreateOrUpdate( - first_factors=[ - FactorIds.OTP_PHONE, - FactorIds.OTP_EMAIL, - FactorIds.LINK_PHONE, - FactorIds.LINK_EMAIL, - FactorIds.THIRDPARTY, - ] - ), - ) - - if response.status != "OK": - print("Handle error") - elif response.created_new: - print("new tenant was created") - else: - print("existing tenant's config was modified.") -``` - - - - - - - - - - - - - - -```bash -curl --location --request PUT '^{coreInjector_uri_without_quotes}/recipe/multitenancy/tenant/v2' \ ---header 'api-key: ^{coreInjector_api_key_without_quotes}' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "tenantId": "customer1", - "firstFactors": ["otp-email", "otp-phone", "link-email", "link-phone", "thirdparty"] -}' -``` - -In the example above, we have added all factor IDs related to the passwordless recipe. However, you can choose to use a subset of them. The factor IDs are described below: -- ThirdParty: `thirdparty` -- Passwordless: - - With email OTP: `otp-email` - - With SMS OTP: `otp-phone` - - With email magic link: `link-email` - - With SMS magic link: `link-phone` - - - - - -```bash -curl --location --request PUT '^{coreInjector_uri_without_quotes}/recipe/multitenancy/tenant' \ ---header 'api-key: ^{coreInjector_api_key_without_quotes}' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "tenantId": "customer1", - "thirdPartyEnabled": true, - "passwordlessEnabled": true -}' -``` - - - - - - - - - - - - -Create Tenant - - - - - - - - - - - -Once a tenant is created, add their thirdparty providers as shown below. - - - - - - - - - - - -```tsx -import Multiteancy from "supertokens-node/recipe/multitenancy"; - -async function createTenant() { - let resp = await Multiteancy.createOrUpdateThirdPartyConfig("customer1", { - thirdPartyId: "active-directory", - name: "Active Directory", - clients: [{ - clientId: "...", - clientSecret: "...", - }], - oidcDiscoveryEndpoint: "https://login.microsoftonline.com//v2.0/.well-known/openid-configuration", - }); - - if (resp.createdNew) { - // Active Directory added to customer1 - } else { - // Existing active directory config overwritten for customer1 - } -} -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/multitenancy" - "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" -) - -func main() { - tenantId := "customer1" - - // highlight-start - resp, err := multitenancy.CreateOrUpdateThirdPartyConfig(tenantId, tpmodels.ProviderConfig{ - ThirdPartyId: "active-directory", - Name: "Active Directory", - Clients: []tpmodels.ProviderClientConfig{ - { - ClientID: "...", - ClientSecret: "...", - }, - }, - OIDCDiscoveryEndpoint: "https://login.microsoftonline.com//v2.0/.well-known/openid-configuration", - }, nil, nil) - // highlight-end - - if err != nil { - // handle error - } - if resp.OK.CreatedNew { - // Active Directory added to customer1 - } else { - // Existing active directory config overwritten for customer1 - } -} -``` - - - - - - - -```python -from supertokens_python.recipe.multitenancy.asyncio import create_or_update_third_party_config -from supertokens_python.recipe.thirdparty.provider import ProviderConfig, ProviderClientConfig - -async def update_tenant(): - result = await create_or_update_third_party_config( - "customer1", - config=ProviderConfig( - third_party_id="active-directory", - name="Active Directory", - clients=[ - ProviderClientConfig( - client_id="...", - client_secret="...", - ) - ], - oidc_discovery_endpoint="https://login.microsoftonline.com//v2.0/.well-known/openid-configuration", - ), - ) - - if result.status != "OK": - print("Error adding active directory to tenant") - elif result.created_new: - print("Active directory was added to the tenant") - else: - print("Existing tenant's active directory config was modified") -``` - - - - - -```python -from supertokens_python.recipe.multitenancy.syncio import create_or_update_third_party_config -from supertokens_python.recipe.thirdparty.provider import ProviderConfig, ProviderClientConfig - -result = create_or_update_third_party_config( - "customer1", - config=ProviderConfig( - third_party_id="active-directory", - name="Active Directory", - clients=[ - ProviderClientConfig( - client_id="...", - client_secret="...", - ) - ] - ), - ) - -if result.status != "OK": - print("Error creating or updating tenant") -elif result.created_new: - print("New tenant was created") -else: - print("Existing tenant's config was modified") -``` - - - - - - - - - - -```bash -curl --location --request PUT '^{coreInjector_uri_without_quotes}//recipe/multitenancy/config/thirdparty' \ ---header 'api-key: ^{coreInjector_api_key_without_quotes}' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "config": { - "thirdPartyId": "active-directory", - "name": "Active Directory", - "clients": [ - { - "clientId": "...", - "clientSecret": "..." - } - ], - "oidcDiscoveryEndpoint": "https://login.microsoftonline.com//v2.0/.well-known/openid-configuration" - } -}' -``` - - - - - - - -Create Third Party Provider - - - - - - - - - - - -:::important -The above shows how to add an Active Directory config for the `customer1` tenant. You can see the config structure for all the in built providers [on this page](../common-customizations/sign-in-and-up/provider-config). -::: - - - - - - - -## Step 2: Build your multi tenant a UX flow - -The most common multi tenant flows are: -- [Tenants use a common domain to login](../common-customizations/multi-tenancy/common-domain-login): All tenants login using the same page (for example, `example.com/auth`) and are optionally redirected to their sub domain post login. At the start of the login flow, the customer will have to input their tenantId / workspace URL / identifier - as defined by you, and the login methods shown would be based on their tenantId. -- [Tenants use their sub domain to login](../common-customizations/multi-tenancy/sub-domain-login): Here, each tenant has a sub domain assigned to them (for example `customer1.example.com`, `customer2.example.com`, ...), and they would visit their sub domain to login and access their app. Each sub domain's login experience may be different (as defined by you or the tenant). - -SuperTokens is flexible enough to allow other forms of UX as well, but since the above two flow are most common, we provide dedicated docs for them (see the links above). - -## See also - -- Multi tenant [single domain](../common-customizations/multi-tenancy/common-domain-login) or [sub domain](../common-customizations/multi-tenancy/sub-domain-login) login. -- [See all built in multitenant providers](../common-customizations/sign-in-and-up/provider-config). -- [See how to add custom multitenant provider](../common-customizations/multi-tenancy/custom-provider). -- [SAML login](../common-customizations/saml/what-is-saml). -- [Tenant data isolation](../common-customizations/multi-tenancy/new-tenant-config#providing-additional-configuration-per-tenant). - - diff --git a/v2/thirdpartypasswordless/custom-ui/securing-routes.mdx b/v2/thirdpartypasswordless/custom-ui/securing-routes.mdx index 665ebb50b..adf124a95 100644 --- a/v2/thirdpartypasswordless/custom-ui/securing-routes.mdx +++ b/v2/thirdpartypasswordless/custom-ui/securing-routes.mdx @@ -4,590 +4,8 @@ title: "Securing your API and frontend routes" hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -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" -import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" -import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" -import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" + -# Securing your API and frontend routes - -## Protecting APIs - - - - - - - -### Requiring an active session - -For your APIs that require a user to be logged in, use the `verifySession` middleware - - - - - - -```tsx -import express from "express"; -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import { SessionRequest } from "supertokens-node/framework/express"; - -let app = express(); - -// highlight-start -app.post("/like-comment", verifySession(), (req: SessionRequest, res) => { - let userId = req.session!.getUserId(); - // highlight-end - //.... -}); -``` - - - - -```tsx -import Hapi from "@hapi/hapi"; -import { verifySession } from "supertokens-node/recipe/session/framework/hapi"; -import { SessionRequest } from "supertokens-node/framework/hapi"; - -let server = Hapi.server({ port: 8000 }); - -server.route({ - path: "/like-comment", - method: "post", - //highlight-start - options: { - pre: [ - { - method: verifySession() - }, - ], - }, - handler: async (req: SessionRequest, res) => { - let userId = req.session!.getUserId(); - //highlight-end - //... - } -}) -``` - - - -```tsx -import Fastify from "fastify"; -import { verifySession } from "supertokens-node/recipe/session/framework/fastify"; -import { SessionRequest } from "supertokens-node/framework/fastify"; - -let fastify = Fastify(); - -//highlight-start -fastify.post("/like-comment", { - preHandler: verifySession(), -}, (req: SessionRequest, res) => { - let userId = req.session!.getUserId(); - //highlight-end - //.... -}); -``` - - - - -```tsx -import { verifySession } from "supertokens-node/recipe/session/framework/awsLambda"; -import { SessionEventV2 } from "supertokens-node/framework/awsLambda"; - -async function likeComment(awsEvent: SessionEventV2) { - let userId = awsEvent.session!.getUserId(); - //.... -}; - -//highlight-next-line -exports.handler = verifySession(likeComment); -``` - - - - -```tsx -import KoaRouter from "koa-router"; -import { verifySession } from "supertokens-node/recipe/session/framework/koa"; -import { SessionContext } from "supertokens-node/framework/koa"; - -let router = new KoaRouter(); - -//highlight-start -router.post("/like-comment", verifySession(), (ctx: SessionContext, next) => { - let userId = ctx.session!.getUserId(); - //highlight-end - //.... -}); -``` - - - - -```tsx -import { inject, intercept } from "@loopback/core"; -import { RestBindings, MiddlewareContext, post, response } from "@loopback/rest"; -import { verifySession } from "supertokens-node/recipe/session/framework/loopback"; -import { SessionContext } from "supertokens-node/framework/loopback"; - -class LikeComment { - //highlight-start - constructor(@inject(RestBindings.Http.CONTEXT) private ctx: MiddlewareContext) { } - @post("/like-comment") - @intercept(verifySession()) - @response(200) - handler() { - let userId = (this.ctx as SessionContext).session!.getUserId(); - //highlight-end - //.... - } -} -``` - - - - -```tsx -import { superTokensNextWrapper } from 'supertokens-node/nextjs' -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import { SessionRequest } from "supertokens-node/framework/express"; - -// highlight-start -export default async function likeComment(req: SessionRequest, res: any) { - await superTokensNextWrapper( - async (next) => { - await verifySession()(req, res, next); - }, - req, - res - ) - - let userId = req.session!.getUserId(); - // highlight-end - //.... -} -``` - - - - -```tsx -import { NextResponse, NextRequest } from "next/server"; -import SuperTokens from "supertokens-node"; -import { withSession } from "supertokens-node/nextjs"; -// @ts-ignore -import { backendConfig } from "@/app/config/backend"; - -SuperTokens.init(backendConfig()); - -export function POST(request: NextRequest) { - return withSession(request, async (err, session) => { - if (err) { - return NextResponse.json(err, { status: 500 }); - } - const userId = session!.getUserId(); - //.... - return NextResponse.json({}); - }); -} -``` - - - - -```tsx -import { Controller, Post, UseGuards, Session } from "@nestjs/common"; -import { SessionContainer } from "supertokens-node/recipe/session"; -// @ts-ignore -import { AuthGuard } from './auth/auth.guard'; - -@Controller() -export class ExampleController { - @Post('example') - @UseGuards(new AuthGuard()) // For more information about this guard please read our NestJS guide. - async postExample(@Session() session: SessionContainer): Promise { - //highlight-start - let userId = session.getUserId(); - - //highlight-end - //.... - return true; - } -} -``` - - - - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/supertokens/supertokens-golang/recipe/session" -) - -func main() { - _ = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - // Wrap the API handler in session.VerifySession - session.VerifySession(nil, likeCommentAPI).ServeHTTP(rw, r) - }) -} - -func likeCommentAPI(w http.ResponseWriter, r *http.Request) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(r.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/gin-gonic/gin" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" -) - -func main() { - router := gin.New() - - // Wrap the API handler in session.VerifySession - router.POST("/likecomment", verifySession(nil), likeCommentAPI) -} - -// This is a function that wraps the supertokens verification function -// to work the gin -func verifySession(options *sessmodels.VerifySessionOptions) gin.HandlerFunc { - return func(c *gin.Context) { - session.VerifySession(options, func(rw http.ResponseWriter, r *http.Request) { - c.Request = c.Request.WithContext(r.Context()) - c.Next() - })(c.Writer, c.Request) - // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly - c.Abort() - } -} - -func likeCommentAPI(c *gin.Context) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(c.Request.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/go-chi/chi" - "github.com/supertokens/supertokens-golang/recipe/session" -) - -func main() { - r := chi.NewRouter() - - // Wrap the API handler in session.VerifySession - r.Post("/likecomment", session.VerifySession(nil, likeCommentAPI)) -} - -func likeCommentAPI(w http.ResponseWriter, r *http.Request) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(r.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/gorilla/mux" - "github.com/supertokens/supertokens-golang/recipe/session" -) - -func main() { - router := mux.NewRouter() - - // Wrap the API handler in session.VerifySession - router.HandleFunc("/likecomment", session.VerifySession(nil, likeCommentAPI)).Methods(http.MethodPost) -} - -func likeCommentAPI(w http.ResponseWriter, r *http.Request) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(r.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - - - - - - -```python -from supertokens_python.recipe.session.framework.fastapi import verify_session -from supertokens_python.recipe.session import SessionContainer -from fastapi import Depends - -# highlight-start -@app.post('/like_comment') # type: ignore -async def like_comment(session: SessionContainer = Depends(verify_session())): - user_id = session.get_user_id() - # highlight-end - - print(user_id) -``` - - - - -```python -from supertokens_python.recipe.session.framework.flask import verify_session -from supertokens_python.recipe.session import SessionContainer -from flask import g - -# highlight-start -@app.route('/update-jwt', methods=['POST']) # type: ignore -@verify_session() -def like_comment(): - session: SessionContainer = g.supertokens # type: ignore - - user_id = session.get_user_id() - # highlight-end - - print(user_id) -``` - - - - -```python -from supertokens_python.recipe.session.framework.django.asyncio import verify_session -from django.http import HttpRequest -from supertokens_python.recipe.session import SessionContainer - -# highlight-start -@verify_session() -async def like_comment(request: HttpRequest): - session: SessionContainer = request.supertokens # type: ignore - - user_id = session.get_user_id() - # highlight-end - - print(user_id) -``` - - - - - - - -The `verifySession` function returns a `401` to the frontend if a session doesn't exist, or if the access token has expired, in which case, our frontend SDK automatically refreshes the session. - -In case of successful session verification, you get access to a `session` object using which you can get the user's ID, or manipulate the session information. - - - - - - - -### Microservice authentication - -For authentication between microservices on your backend, checkout the [microservice auth guide](/docs/microservice_auth/client-credentials). - - - -## Protecting frontend routes - - - - - - - - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```tsx -import Session from 'supertokens-web-js/recipe/session'; - -async function doesSessionExist() { - if (await Session.doesSessionExist()) { - // user is logged in - } else { - // user has not logged in yet - } -} -``` - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```tsx -import Session from 'supertokens-web-js-script/recipe/session'; -async function doesSessionExist() { - if (await Session.doesSessionExist()) { - // user is logged in - } else { - // user has not logged in yet - } -} -``` - - - - - - - - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```tsx -import SuperTokens from 'supertokens-react-native'; - -async function doesSessionExist() { - if (await SuperTokens.doesSessionExist()) { - // user is logged in - } else { - // user has not logged in yet - } -} -``` - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```kotlin -import android.app.Application -import com.supertokens.session.SuperTokens - -class MainApplication: Application() { - fun doesSessionExist() { - if (SuperTokens.doesSessionExist(this.applicationContext)) { - // user is logged in - } else { - // user has not logged in yet - } - } -} -``` - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```swift -import UIKit -import SuperTokensIOS - -fileprivate class ViewController: UIViewController { - func doesSessionExist() { - if SuperTokens.doesSessionExist() { - // User is logged in - } else { - // User is not logged in - } - } -} -``` - - - - - -You can use the `doesSessionExist` function to check if a session exists. - -```dart -import 'package:supertokens_flutter/supertokens.dart'; - -Future doesSessionExist() async { - return await SuperTokens.doesSessionExist(); -} -``` - - - - - - - - - - - - - - - -## See also - -- Optional sessions [for APIs](../common-customizations/sessions/session-verification-in-api/verify-session#optional-session-verification) and [the frontend](../common-customizations/sessions/checking-session-front-end) -- [Verifying session without using a middleware](../common-customizations/sessions/session-verification-in-api/get-session) -- Session claim validation [for APIs](../common-customizations/sessions/claims/claim-validators) and [the frontend](../common-customizations/sessions/claims/claim-validators) -- [Changing session lifetime](../common-customizations/sessions/change-session-timeout) -- [Sharing session across sub domains](../common-customizations/sessions/share-sessions-across-sub-domains) - - diff --git a/v2/thirdpartypasswordless/custom-ui/sign-out.mdx b/v2/thirdpartypasswordless/custom-ui/sign-out.mdx index af9169fa0..81b1a86e9 100644 --- a/v2/thirdpartypasswordless/custom-ui/sign-out.mdx +++ b/v2/thirdpartypasswordless/custom-ui/sign-out.mdx @@ -4,135 +4,9 @@ title: Implementing sign out hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" -import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" -import TabItem from '@theme/TabItem'; -import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" -# Implementing sign out + -The `signOut` method revokes the session on the frontend and on the backend. Calling this function without a valid session also yields a successful response. - - - - - - -```tsx -import Session from "supertokens-web-js/recipe/session"; - -async function logout () { - // highlight-next-line - await Session.signOut(); - window.location.href = "/auth"; // or to wherever your logic page is -} -``` - - - - -```tsx -import supertokensSession from "supertokens-web-js-script/recipe/session"; -async function logout () { - // highlight-next-line - await supertokensSession.signOut(); - window.location.href = "/auth"; // or to wherever your logic page is -} -``` - - - - - - - - - - - - -```tsx -import SuperTokens from "supertokens-react-native"; - -async function logout () { - // highlight-next-line - await SuperTokens.signOut(); - // navigate to the login screen.. -} -``` - - - - - -```kotlin -import android.app.Application -import com.supertokens.session.SuperTokens - -class MainApplication: Application() { - fun logout() { - // highlight-next-line - SuperTokens.signOut(this); - // navigate to the login screen.. - } -} -``` - - - - - -```swift -import UIKit -import SuperTokensIOS - -fileprivate class ViewController: UIViewController { - func signOut() { - SuperTokens.signOut(completionHandler: { - error in - - if error != nil { - // handle error - } else { - // Signed out successfully - } - }) - } -} -``` - - - - - -```dart -import 'package:supertokens_flutter/supertokens.dart'; - -Future signOut() async { - await SuperTokens.signOut( - completionHandler: (error) { - // handle error if any - } - ); -} -``` - - - - - - - - - -- On success, the `signOut` function does not redirect the user to another page, so you must redirect the user yourself. -- The `signOut` function calls the signout API exposed by the session recipe on the backend. -- If you call the `signOut` function whilst the access token has expired, but the refresh token still exists, our SDKs will do an automatic session refresh before revoking the session. - -## See also - -- [Revoking a session on the backend](../common-customizations/sessions/revoke-session) diff --git a/v2/thirdpartypasswordless/custom-ui/thirdparty-login.mdx b/v2/thirdpartypasswordless/custom-ui/thirdparty-login.mdx index e21d8894c..903281af2 100644 --- a/v2/thirdpartypasswordless/custom-ui/thirdparty-login.mdx +++ b/v2/thirdpartypasswordless/custom-ui/thirdparty-login.mdx @@ -4,1179 +4,9 @@ title: Social login hide_title: true --- - - +import Redirector from '/src/components/Redirector'; - - - -import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" -import TabItem from '@theme/TabItem'; -import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" -import AppInfoForm from "/src/components/appInfoForm" -import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; + -# Social login -There are different flows that the third party login provider may support: -- _**Flow 1)**_ Via authorisation code (one time use code) - for web and mobile apps. - - _**a)**_ If you have configred the client secret on the backend: - - The frontend sends the auth code to the backend, the backend exchanges that with the provided client secret to get the access token. The access token is then used to fetch user info and log them in. - - _**b)**_ If you have **not** provided the client secret on the backend: - - The backend uses PKCE flow to exchange the auth code with the user's access token. The access token is then used to fetch user info and log them in. -- _**Flow 2)**_ Via OAuth / access tokens - for mobile apps. - - The access token is available on the frontend and is sent to the backend. SuperTokens then fetches user info using the access token and logs them in. - -:::note -The same flow applies during sign up and sign in. If the user is signing up, the `createdNewUser` boolean on the frontend and backend will be `true` (as the result of the sign in up API call). -::: - - - - -

      Flow 1a: Authorization code grant flow (Sign in with Google example)

      - -

      Step 1) Redirecting to social / SSO provider

      - -The first step is to fetch the URL on which the user will be authenticated. This can be done by querying the backend API exposed by SuperTokens (as shown below). The backend SDK automatically appends the right query params to the URL (like scope, client ID etc). - -After we get the URL, we simply redirect the user there. In the code below, we will take an example of login with Google: - - - - -```tsx -import { getAuthorisationURLWithQueryParamsAndSetState } from "supertokens-web-js/recipe/thirdparty"; - -async function googleSignInClicked() { - try { - const authUrl = await getAuthorisationURLWithQueryParamsAndSetState({ - thirdPartyId: "google", - - // This is where Google should redirect the user back after login or error. - // This URL goes on the Google's dashboard as well. - frontendRedirectURI: "http:///auth/callback/google", - }); - - /* - Example value of authUrl: https://accounts.google.com/o/oauth2/v2/auth/oauthchooseaccount?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email&access_type=offline&include_granted_scopes=true&response_type=code&client_id=1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com&state=5a489996a28cafc83ddff&redirect_uri=https%3A%2F%2Fsupertokens.io%2Fdev%2Foauth%2Fredirect-to-app&flowName=GeneralOAuthFlow - */ - - // we redirect the user to google for auth. - window.location.assign(authUrl); - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - -```tsx -import supertokensThirdParty from "supertokens-web-js-script/recipe/thirdparty"; -async function googleSignInClicked() { - try { - const authUrl = await supertokensThirdParty.getAuthorisationURLWithQueryParamsAndSetState({ - thirdPartyId: "google", - - // This is where Google should redirect the user back after login or error. - // This URL goes on the Google's dashboard as well. - frontendRedirectURI: "http:///auth/callback/google", - }); - - /* - Example value of authUrl: https://accounts.google.com/o/oauth2/v2/auth/oauthchooseaccount?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email&access_type=offline&include_granted_scopes=true&response_type=code&client_id=1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com&state=5a489996a28cafc83ddff&redirect_uri=https%3A%2F%2Fsupertokens.io%2Fdev%2Foauth%2Fredirect-to-app&flowName=GeneralOAuthFlow - */ - - // we redirect the user to google for auth. - window.location.assign(authUrl); - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - -

      Step 2) Handling the auth callback on your frontend

      - -Once the third party provider redirects your user back to your app, you need to consume the information to sign in the user. This requires you to: -- Setup a route in your app that will handle this callback. We recommend something like `http:///auth/callback/google` (for Google). Regardless of what you make this path, remember to use that same path when calling the `getAuthorisationURLWithQueryParamsAndSetState` function in the first step. - -- On that route, call the following function on page load - - - - - ```tsx - import { signInAndUp } from "supertokens-web-js/recipe/thirdparty"; - - async function handleGoogleCallback() { - try { - const response = await signInAndUp(); - - if (response.status === "OK") { - console.log(response.user) - if (response.createdNewRecipeUser && response.user.loginMethods.length === 1) { - // sign up successful - } else { - // sign in successful - } - window.location.assign("/home"); - } else if (response.status === "SIGN_IN_UP_NOT_ALLOWED") { - // the reason string is a user friendly message - // about what went wrong. It can also contain a support code which users - // can tell you so you know why their sign in / up was not allowed. - window.alert(response.reason) - } else { - // SuperTokens requires that the third party provider - // gives an email for the user. If that's not the case, sign up / in - // will fail. - - // As a hack to solve this, you can override the backend functions to create a fake email for the user. - - window.alert("No email provided by social login. Please use another form of login"); - window.location.assign("/auth"); // redirect back to login page - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } - } - ``` - - - - - ```tsx - import supertokensThirdParty from "supertokens-web-js-script/recipe/thirdparty"; - async function handleGoogleCallback() { - try { - const response = await supertokensThirdParty.signInAndUp(); - - if (response.status === "OK") { - console.log(response.user) - if (response.createdNewRecipeUser && response.user.loginMethods.length === 1) { - // sign up successful - } else { - // sign in successful - } - window.location.assign("/home"); - } else if (response.status === "SIGN_IN_UP_NOT_ALLOWED") { - // the reason string is a user friendly message - // about what went wrong. It can also contain a support code which users - // can tell you so you know why their sign in / up was not allowed. - window.alert(response.reason) - } else { - // SuperTokens requires that the third party provider - // gives an email for the user. If that's not the case, sign up / in - // will fail. - - // As a hack to solve this, you can override the backend functions to create a fake email for the user. - - window.alert("No email provided by social login. Please use another form of login"); - window.location.assign("/auth"); // redirect back to login page - } - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } - } - ``` - - - - -:::note -On success, the backend will send back session tokens as part of the response headers which will be automatically handled by our frontend SDK for you. -::: - -

      Special case for login with Apple

      - - - -Unlike other providers, Apple will not redirect your user back to your frontend app. Instead, it will redirect the user to your backend with a `FORM POST` request. This means that the URL that you configure on the Apple's dashboard should point to your backend API layer in which **our middleware** will handle the request and redirect the user to your frontend app. Your frontend app should then call the `signInAndUp` API on that page as shown previously. - -In order to tell SuperTokens which frontend route to redirect the user back to, you need to set the `frontendRedirectURI` to the frontend route (just like for other providers), and also need to set the `redirectURIOnProviderDashboard` to point to your backend API route (to which Apple will send a POST request). - - - - -```tsx -import { getAuthorisationURLWithQueryParamsAndSetState } from "supertokens-web-js/recipe/thirdparty"; - -async function appleSignInClicked() { - try { - const authUrl = await getAuthorisationURLWithQueryParamsAndSetState({ - thirdPartyId: "apple", - - frontendRedirectURI: "http://localhost:3000/auth/callback/apple", // This is an example callback URL on your frontend. You can use another path as well. - // highlight-start - redirectURIOnProviderDashboard: "^{form_apiDomain}^{form_apiBasePath}/callback/apple", // This URL goes on the Apple's dashboard - // highlight-end - }); - - // we redirect the user to apple for auth. - window.location.assign(authUrl); - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - -```tsx -import supertokensThirdParty from "supertokens-web-js-script/recipe/thirdparty"; -async function appleSignInClicked() { - try { - const authUrl = await supertokensThirdParty.getAuthorisationURLWithQueryParamsAndSetState({ - thirdPartyId: "apple", - - frontendRedirectURI: "http://localhost:3000/auth/callback/apple", // This is an example callback URL on your frontend. You can use another path as well. - // highlight-start - redirectURIOnProviderDashboard: "^{form_apiDomain}^{form_apiBasePath}/callback/apple", // This URL goes on the Apple's dashboard - // highlight-end - }); - - /* - Example value of authUrl: https://accounts.google.com/o/oauth2/v2/auth/oauthchooseaccount?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email&access_type=offline&include_granted_scopes=true&response_type=code&client_id=1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com&state=5a489996a28cafc83ddff&redirect_uri=https%3A%2F%2Fsupertokens.io%2Fdev%2Foauth%2Fredirect-to-app&flowName=GeneralOAuthFlow - */ - - // we redirect the user to google for auth. - window.location.assign(authUrl); - } catch (err: any) { - if (err.isSuperTokensGeneralError === true) { - // this may be a custom error message sent from the API by you. - window.alert(err.message); - } else { - window.alert("Oops! Something went wrong."); - } - } -} -``` - - - - - - -

      Flow 1b: Authorization code grant flow with PKCE

      - -This is identical to flow 1a, except that you do **not** need to provide a client secret during backend init. This flow only works for providers which support the [PKCE flow](https://oauth.net/2/pkce/). - -

      Flow 2: Via OAuth / Access token

      - -:::caution -This flow is not applicable for web apps. -::: - -
      - - - -

      Flow 1a: Authorization code grant flow

      - -

      Sign in with Apple example

      - - - - - -

      Step 1) Fetching the authorisation token on the frontend

      - -For react native apps, this involves setting up the [react-native-apple-authentication library](https://github.com/invertase/react-native-apple-authentication) in your app. Checkout their `README` for steps on how to integrate their SDK into your application. The minimum scope required by SuperTokens is the one that gives the user's email (In case of Apple, that could be the user's actual email or the proxy email provided by Apple - it doesn't really matter). - -Once the integration is done, you should call the `appleAuth.performRequest` function for iOS and the `appleAuthAndroid.signIn` function for Android. Either way, the result of the function will be a one time use auth code which you should send to your backend as shown in the next step. - -A full example of this can be found in [our example app](https://github.com/supertokens/supertokens-react-native/blob/master/examples/with-thirdparty/apple.ts). - -In case you are using Expo, you can use the [expo-apple-authentication](https://docs.expo.dev/versions/latest/sdk/apple-authentication/) library instead (not that this library only works on iOS). - -
      - - - -

      Step 1) Fetching the authorisation token on the frontend

      - -:::info -Coming Soon -::: - -
      - - - -

      Step 1) Fetching the authorisation token on the frontend

      - -For iOS you use the normal sign in with apple flow and then use the authorization code to login with SuperTokens. You can see a full example of this in the `onAppleClicked` function in [our example app](https://github.com/supertokens/supertokens-ios/blob/master/examples/with-thirdparty/with-thirdparty/LoginScreen/LoginScreenViewController.swift). - -```swift -import UIKit -import AuthenticationServices - -fileprivate class ViewController: UIViewController, ASAuthorizationControllerPresentationContextProviding, ASAuthorizationControllerDelegate { - func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor { - return view.window! - } - - func loginWithApple() { - let authorizationRequest = ASAuthorizationAppleIDProvider().createRequest() - authorizationRequest.requestedScopes = [.email, .fullName] - - let authorizationController = ASAuthorizationController(authorizationRequests: [authorizationRequest]) - - authorizationController.presentationContextProvider = self - authorizationController.delegate = self - authorizationController.performRequests() - } - - func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { - guard let credential: ASAuthorizationAppleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential, - let authorizationCode = credential.authorizationCode, - let authorizationCodeString: String = String(data: authorizationCode, encoding: .utf8), - let email: String = credential.email as? String, - let nameComponents: PersonNameComponents = credential.fullName as? PersonNameComponents, - let firstName: String = nameComponents.givenName as? String, - let lastName: String = nameComponents.familyName as? String else {return} - - // Send the user information and auth code to the backend. Refer to the next step. - } -} -``` - -
      - - - -

      Step 1) Fetching the authorisation token on the frontend

      - -For flutter we use the [sign_in_with_apple](https://pub.dev/packages/sign_in_with_apple) package, make sure to follow the prerequisites steps to get the package setup. After setup use the snippet below to trigger the apple sign in flow. You can see a full example of this in the `loginWithApple` function in [our example app](https://github.com/supertokens/supertokens-flutter/blob/master/examples/with-thirdparty/lib/screens/login.dart). - -```dart -import 'package:sign_in_with_apple/sign_in_with_apple.dart'; - -void loginWithApple() async { - try { - var credential = await SignInWithApple.getAppleIDCredential( - scopes: [ - AppleIDAuthorizationScopes.email, - AppleIDAuthorizationScopes.fullName, - ], - // Required for Android only - webAuthenticationOptions: WebAuthenticationOptions( - clientId: "", - redirectUri: Uri.parse( - "//callback/apple", - ), - ), - ); - - String authorizationCode = credential.authorizationCode; - String? idToken = credential.identityToken; - String? email = credential.email; - String? firstname = credential.givenName; - String? lastName = credential.familyName; - - // Send the user information and auth code to the backend. Refer to the next step. - } catch (e) { - // Sign in aborted or failed - } -} -``` - -In the snippet above for Android we need to pass an additional `webAuthenticationOptions` property when signing in with Apple. This is because on Android the library uses the web login flow and we need to provide it with the client id and redirection uri. The `redirectUri` property here is the URL to which Apple will make a `POST` request after the user has logged in. The SuperTokens backend SDKs provide an API for this at `//callback/apple`. - -

      Additional steps for Android

      - -For android we also need to provide a way for the web login flow to redirect back to the app. By default the API provided by the backend SDKs redirect to the website domain you provide when initialising the SDK, we can override the API to have it redirect to our app instead. For example if you were using the Node.js SDK: - - - - - -```tsx -import ThirdParty from "supertokens-node/recipe/thirdparty"; - -ThirdParty.init({ - // highlight-start - override: { - apis: (original) => { - return { - ...original, - appleRedirectHandlerPOST: async (input) => { - if (original.appleRedirectHandlerPOST === undefined) { - throw Error("Should never come here"); - } - - // inut.formPostInfoFromProvider contains all the query params attached by Apple - const stateInBase64 = input.formPostInfoFromProvider.state; - - // The web SDKs add a default state - if (stateInBase64 === undefined) { - // Redirect to android app - // We create a dummy URL to create the query string - const dummyUrl = new URL("http://localhost:8080"); - for (const [key, value] of Object.entries(input.formPostInfoFromProvider)) { - dummyUrl.searchParams.set(key, `${value}`); - } - - const queryString = dummyUrl.searchParams.toString(); - // Refer to the README of sign_in_with_apple to understand what this url is - const redirectUrl = `intent://callback?${queryString}#Intent;package=YOUR.PACKAGE.IDENTIFIER;scheme=signinwithapple;end`; - - input.options.res.setHeader("Location", redirectUrl, false); - input.options.res.setStatusCode(303); - input.options.res.sendHTMLResponse(""); - } else { - // For the web flow we can use the original implementation - original.appleRedirectHandlerPOST(input); - } - }, - }; - }, - }, - // highlight-end -}) -``` - - - - - -```go -import ( - "net/http" - "strings" - - "github.com/supertokens/supertokens-golang/recipe/thirdparty" - "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" -) - -func main() { - thirdparty.Init(&tpmodels.TypeInput{ - Override: &tpmodels.OverrideStruct{ - // highlight-start - APIs: func(originalImplementation tpmodels.APIInterface) tpmodels.APIInterface { - originalAppleRedirectPost := *originalImplementation.AppleRedirectHandlerPOST - - *originalImplementation.AppleRedirectHandlerPOST = func(formPostInfoFromProvider map[string]interface{}, options tpmodels.APIOptions, userContext *map[string]interface{}) error { - // formPostInfoFromProvider contains all the query params attached by Apple - state, stateOk := formPostInfoFromProvider["state"].(string) - - queryParams := []string{} - if (!stateOk) || state == "" { - // Redirect to android app - for key, value := range formPostInfoFromProvider { - queryParams = append(queryParams, key+"="+value.(string)) - } - - queryString := "" - if len(queryParams) > 0 { - queryString = strings.Join(queryParams, "&") - } - - // Refer to the README of sign_in_with_apple to understand what this url is - redirectUri := "intent://callback?" + queryString + "#Intent;package=YOUR.PACKAGE.IDENTIFIER;scheme=signinwithapple;end" - - options.Res.Header().Set("Location", redirectUri) - options.Res.WriteHeader(http.StatusSeeOther) - return nil - } else { - return originalAppleRedirectPost(formPostInfoFromProvider, options, userContext) - } - } - - return originalImplementation - }, - }, - // highlight-end - }) -} -``` - - - - - -```python -from supertokens_python.recipe import thirdparty -from supertokens_python.recipe.thirdparty.interfaces import APIInterface, APIOptions -from typing import Dict, Any - -# highlight-start -def override_thirdparty_apis(original_implementation: APIInterface): - original_apple_redirect_post = original_implementation.apple_redirect_handler_post - - async def apple_redirect_handler_post( - form_post_info: Dict[str, Any], - api_options: APIOptions, - user_context: Dict[str, Any] - ): - # form_post_info contains all the query params attached by Apple - state = form_post_info["state"] - - # The web SDKs add a default state - if state is None: - query_items = [ - f"{key}={value}" for key, value in form_post_info.items() - ] - - query_string = "&".join(query_items) - - # Refer to the README of sign_in_with_apple to understand what this url is - redirect_url = "intent://callback?" + query_string + "#Intent;package=YOUR.PACKAGE.IDENTIFIER;scheme=signinwithapple;end" - - api_options.response.set_header("Location", redirect_url) - api_options.response.set_status_code(303) - api_options.response.set_html_content("") - else: - return await original_apple_redirect_post(form_post_info, api_options, user_context) - - original_implementation.apple_redirect_handler_post = apple_redirect_handler_post - return original_implementation -# highlight-end - -thirdparty.init( - # highlight-start - override=thirdparty.InputOverrideConfig( - apis=override_thirdparty_apis - ), - # highlight-end -) -``` - - - - - -In the code above we override the `appleRedirectHandlerPOST` API to check if the request was made by our Android app (You can skip checking the state if you only have a mobile app and no website). `sign_in_with_apple` requires us to parse the query params sent by apple and include them in the redirect URL in a specific way, and then we simply redirect to the deep link url. Refer to the README for `sign_in_with_apple` to read about the deep link setup required in Android. - -
      - -
      - -

      Step 2) Calling the signinup API to consume the authorisation token

      - - - -Once you have the authorisation code from the auth provider, you need to call the signinup API exposed by our backend SDK as shown below: - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup' \ ---header 'Content-Type: application/json; charset=utf-8' \ ---data-raw '{ - "thirdPartyId": "apple", - "clientType": "...", - "redirectURIInfo": { - "redirectURIOnProviderDashboard": "^{form_apiDomain}^{form_apiBasePath}/callback/apple", - "redirectURIQueryParams": { - "code": "...", - "user": { - "name":{ - "firstName":"...", - "lastName":"..." - }, - "email":"..." - } - } - } -}' -``` - -:::important -- On iOS, the client id set in the backend should be the same as the bundle identifier for your app. -- The `clientType` input is optional, and is required only if you have initialised more than one client in the provide on the backend (See the "Social / SSO login for both, web and mobile apps" section below). -- On iOS, `redirectURIOnProviderDashboard` doesn't matter and its value can be a universal link configured for your app. -- On Android, the `redirectURIOnProviderDashboard` should match the one configured on the Apple developer dashboard. -- The `user` object contains information provided by Apple. -::: - -The response body from the API call has a `status` property in it: -- `status: "OK"`: User sign in / up was successful. The response also contains more information about the user, for example their user ID, and if it was a new user or existing user. -- `status: "NO_EMAIL_GIVEN_BY_PROVIDER"`: This is returned if the social / SSO provider did not provider an email for the user. In this case, you want to ask the user to pick another method of sign in. Or, you can also override the backend functions to create a fake email for the user for this provider. -- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. -- `status: "SIGN_IN_UP_NOT_ALLOWED"`: This can happen during automatic account linking or during MFA. The `reason` prop that's in the response body contains a support code using which you can see why the sign in / up was not allowed. - -:::note -On success, the backend will send back session tokens as part of the response headers which will be automatically handled by our frontend SDK for you. -::: - - - -

      Sign in with Google Example

      - - - - - -

      Step 1) Fetching the authorisation token on the frontend

      - -This involves setting up the [@react-native-google-signin/google-signin](https://github.com/react-native-google-signin/google-signin) in your app. Checkout their `README` for steps on how to integrate their SDK into your application. The minimum scope required by SuperTokens is the one that gives the user's email. - -Once the library is set up, use `GoogleSignin.configure` and `GoogleSignin.signIn` to trigger the login flow and sign the user in with Google. Refer to [our example app](https://github.com/supertokens/supertokens-react-native/blob/master/examples/with-thirdparty/google.ts) to see the full code for this. - -```tsx -import { GoogleSignin } from "@react-native-google-signin/google-signin"; - -export const performGoogleSignIn = async (): Promise => { - GoogleSignin.configure({ - webClientId: "GOOGLE_WEB_CLIENT_ID", - iosClientId: "GOOGLE_IOS_CLIENT_ID", - }); - - try { - const user = await GoogleSignin.signIn({}); - const authCode = user.serverAuthCode; - - // Refer to step 2 - - return true; - } catch (e) { - console.log("Google sign in failed with error", e); - } - - return false; -}; -``` - -
      - - - -

      Step 1) Fetching the authorisation token on the frontend

      - -Follow the [official Google Sign In guide](https://developers.google.com/identity/sign-in/android/start-integrating) to set up their library and sign the user in with Google. Fetch the authorization code from the google sign in result. For a full example refer to the `signInWithGoogle` function in [our example app](https://github.com/supertokens/supertokens-android/blob/master/examples/with-thirdparty/app/src/main/java/com/supertokens/supertokensexample/LoginActivity.kt). - -```kotlin -import androidx.activity.result.ActivityResult -import androidx.activity.result.ActivityResultLauncher -import androidx.activity.result.contract.ActivityResultContracts -import androidx.appcompat.app.AppCompatActivity -import android.os.Bundle -import android.util.Log -import com.google.android.gms.auth.api.signin.GoogleSignIn -import com.google.android.gms.auth.api.signin.GoogleSignInOptions -import android.content.Intent - -class LoginActivity : AppCompatActivity() { - private lateinit var googleResultLauncher: ActivityResultLauncher - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - googleResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - onGoogleResultReceived(it) - } - } - - private fun signInWithGoogle() { - val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) - .requestServerAuthCode("GOOGLE_WEB_CLIENT_ID") - .requestEmail() - .build() - - val googleClient = GoogleSignIn.getClient(this, gso) - val signInIntent = googleClient.signInIntent - - googleResultLauncher.launch(signInIntent) - } - - private fun onGoogleResultReceived(it: ActivityResult) { - val task = GoogleSignIn.getSignedInAccountFromIntent(it.data) - val account = task.result - val authCode = account.serverAuthCode - - // Refer to step 2 - } -} -``` - -
      - - - -

      Step 1) Fetching the authorisation token on the frontend

      - -For iOS we use the `GoogleSignIn` library, follow the [official guide](https://developers.google.com/identity/sign-in/ios/start-integrating) to set up the library and sign the user in with Google. Use the result of google sign in to get the authorization code. For a full example refer to the `onGoogleCliked` function in [our example app](https://github.com/supertokens/supertokens-ios/blob/master/examples/with-thirdparty/with-thirdparty/LoginScreen/LoginScreenViewController.swift). - -```swift -import UIKit -import GoogleSignIn - -fileprivate class LoginScreenViewController: UIViewController { - @IBAction func onGoogleCliked() { - GIDSignIn.sharedInstance.signIn(withPresenting: self) { signInResult, error in - guard error == nil else { return } - - guard let authCode: String = signInResult?.serverAuthCode as? String else { - print("Google login did not return an authorisation code") - return - } - - // Refer to step 2 - } - } -} -``` - -
      - - - -

      Step 1) Fetching the authorisation token on the frontend

      - -For flutter we use the [google_sign_in](https://pub.dev/packages/google_sign_in) package, make sure to follow the prerequisites steps to get the package setup. After setup use the snippet below to trigger the google sign in flow. For a full example refer to the `loginWithGoogle` in [our example app](https://github.com/supertokens/supertokens-flutter/blob/master/examples/with-thirdparty/lib/screens/login.dart). - -```dart -import 'package:google_sign_in/google_sign_in.dart'; -import 'dart:io'; - -Future loginWithGoogle() async { - GoogleSignIn googleSignIn; - - if (Platform.isAndroid) { - googleSignIn = GoogleSignIn( - serverClientId: "GOOGLE_WEB_CLIENT_ID", - scopes: [ - 'email', - ], - ); - } else { - googleSignIn = GoogleSignIn( - clientId: "GOOGLE_IOS_CLIENT_ID", - serverClientId: "GOOGLE_WEB_CLIENT_ID", - scopes: [ - 'email', - ], - ); - } - - GoogleSignInAccount? account = await googleSignIn.signIn(); - - if (account == null) { - print("Google sign in was aborted"); - return; - } - - String? authCode = account.serverAuthCode; - - if (authCode == null) { - print("Google sign in did not return a server auth code"); - return; - } - - // Refer to step 2 - } -``` - -
      - -
      - -

      Step 2) Calling the signinup API to consume the authorisation token

      - - - -Once you have the authorisation code from the auth provider, you need to call the signinup API exposed by our backend SDK as shown below: - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup' \ ---header 'Content-Type: application/json; charset=utf-8' \ ---data-raw '{ - "thirdPartyId": "google", - "clientType": "...", - "redirectURIInfo": { - "redirectURIOnProviderDashboard": "", - "redirectURIQueryParams": { - "code": "...", - } - } -}' -``` - -:::important -When calling the API exposed by the SuperTokens backend SDK, we pass an empty string for `redirectURIOnProviderDashboard` because we use the native login flow using the authorization code which does not involve any redirection on the frontend. -::: - -The response body from the API call has a `status` property in it: -- `status: "OK"`: User sign in / up was successful. The response also contains more information about the user, for example their user ID, and if it was a new user or existing user. -- `status: "NO_EMAIL_GIVEN_BY_PROVIDER"`: This is returned if the social / SSO provider did not provider an email for the user. In this case, you want to ask the user to pick another method of sign in. Or, you can also override the backend functions to create a fake email for the user for this provider. -- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. -- `status: "SIGN_IN_UP_NOT_ALLOWED"`: This can happen during automatic account linking or during MFA. The `reason` prop that's in the response body contains a support code using which you can see why the sign in / up was not allowed. - -:::note -On success, the backend will send back session tokens as part of the response headers which will be automatically handled by our frontend SDK for you. -::: - - - -

      Flow 1b: Authorization code grant flow with PKCE

      - -This is similar to flow 1a, except that you do **not** need to provide a client secret during backend init. This flow only works for providers which support the [PKCE flow](https://oauth.net/2/pkce/). - - - - - -

      Step 1) Fetching the authorisation token on the frontend

      - -You can use the [react native auth library](https://github.com/FormidableLabs/react-native-app-auth) to also return the PKCE code verifier along with the authorization code. This can be done by setting the `usePKCE` boolean to `true` and also by setting the `skipCodeExchange` to `true` when configuring the react native auth library. - -
      - - - -

      Step 1) Fetching the authorisation token on the frontend

      - -You can use the [AppAuth-Android](https://github.com/openid/AppAuth-Android) library to use the PKCE flow by using the `setCodeVerifier` method when creating a `AuthorizationRequest`. - -
      - - - -

      Step 1) Fetching the authorisation token on the frontend

      - -You can use the [AppAuth-iOS](https://github.com/openid/AppAuth-iOS) library to use the PKCE flow. - -
      - - - -

      Step 1) Fetching the authorisation token on the frontend

      - -You can use [flutter_appauth](https://pub.dev/packages/flutter_appauth) to use the PKCE flow by providing a `codeVerifier` when you call the `appAuth.token` function. - -
      - -
      - -

      Step 2) Calling the signinup API to consume the authorisation token

      - - - -Once you have the authorisation code and PKCE verifier from the auth provider, you need to call the signinup API exposed by our backend SDK as shown below: - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "thirdPartyId": "THIRD_PARTY_ID", - "clientType": "...", - "redirectURIInfo": { - "redirectURIOnProviderDashboard": "REDIRECT_URI", - "redirectURIQueryParams": { - "code": "...", - }, - "pkceCodeVerifier": "..." - } -}' -``` - -:::important -- Replace `THIRD_PARTY_ID` with the provider id. The provider id must match the one you configure in the backend when intialising SuperTokens. -- `REDIRECT_URI` must exactly match the value you configure on the providers dashboard. -::: - -The response body from the API call has a `status` property in it: -- `status: "OK"`: User sign in / up was successful. The response also contains more information about the user, for example their user ID, and if it was a new user or existing user. -- `status: "NO_EMAIL_GIVEN_BY_PROVIDER"`: This is returned if the social / SSO provider did not provider an email for the user. In this case, you want to ask the user to pick another method of sign in. Or, you can also override the backend functions to create a fake email for the user for this provider. -- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. -- `status: "SIGN_IN_UP_NOT_ALLOWED"`: This can happen during automatic account linking or during MFA. The `reason` prop that's in the response body contains a support code using which you can see why the sign in / up was not allowed. - -:::note -On success, the backend will send back session tokens as part of the response headers which will be automatically handled by our frontend SDK for you. -::: - - - ----------------- - -

      Flow 2: Via OAuth / Access token

      - -

      Step 1) Fetching the OAuth / access tokens on the frontend

      - -1. Sign in with the social provider. The minimum required scope is the one that provides access to the user's email. You can use any library to sign in with the social provider. -2. Get the access token on the frontend if it is available. -3. Get the id token from the sign in result if it is available. - -:::important -You need to provide either the access token or the id token, or both in step 2, depending on what is available. -::: - -

      Step 2) Calling the signinup API to use the OAuth tokens

      - - - -Once you have the `access_token` or the `id_token` from the auth provider, you need to call the signinup API exposed by our backend SDK as shown below: - -```bash -curl --location --request POST '^{form_apiDomain}^{form_apiBasePath}/signinup' \ ---header 'Content-Type: application/json; charset=utf-8' \ ---data-raw '{ - "thirdPartyId": "google", - "clientType": "...", - "oAuthTokens": { - "access_token": "...", - "id_token": "..." - }, -}' -``` - -:::important -- The `clientType` input is optional, and is required only if you have initialised more than one client in the provide on the backend (See the "Social / SSO login for both, web and mobile apps" section below). -- If you have the `id_token`, you can send that along with the `access_token`. -::: - -The response body from the API call has a `status` property in it: -- `status: "OK"`: User sign in / up was successful. The response also contains more information about the user, for example their user ID, and if it was a new user or existing user. -- `status: "NO_EMAIL_GIVEN_BY_PROVIDER"`: This is returned if the social / SSO provider did not provider an email for the user. In this case, you want to ask the user to pick another method of sign in. Or, you can also override the backend functions to create a fake email for the user for this provider. -- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend. -- `status: "SIGN_IN_UP_NOT_ALLOWED"`: This can happen during automatic account linking or during MFA. The `reason` prop that's in the response body contains a support code using which you can see why the sign in / up was not allowed. - -:::note -On success, the backend will send back session tokens as part of the response headers which will be automatically handled by our frontend SDK for you. -::: - - - -
      - -
      - -## Social / SSO login for both, web and mobile apps - -If you have social / SSO login for your web and mobile app, then you might need to setup different client ID / secret for the same provider on the backend. For example, in case of Apple login, Apple gives you different client IDs for iOS login vs web & Android login (same client ID for web and Android). - -In order to get this to work, you would need to add additional clients to the Apple.init on the backend. Each client would need to be uniquely identified and this is done using the `clientType` string. For example, you can add one `clientType` for `web-and-android` and one for `ios`. - - - - -```tsx -import { ProviderInput } from "supertokens-node/recipe/thirdparty/types"; - -let providers: ProviderInput[] = [ - { - config: { - thirdPartyId: "apple", - clients: [{ - clientType: "web-and-android", - clientId: "...", - additionalConfig: { - "keyId": "...", - "privateKey": "...", - "teamId": "...", - } - }, { - clientType: "ios", - clientId: "...", - additionalConfig: { - "keyId": "...", - "privateKey": "...", - "teamId": "...", - } - }] - } - } -] -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" -) - -func main() { - _ = []tpmodels.ProviderInput{{ - Config: tpmodels.ProviderConfig{ - ThirdPartyId: "apple", - Clients: []tpmodels.ProviderClientConfig{ - { - ClientType: "web-and-android", - ClientID: "...", - AdditionalConfig: map[string]interface{}{ - "keyId": "...", - "privateKey": "...", - "teamId": "...", - }, - }, - { - ClientType: "ios", - ClientID: "...", - AdditionalConfig: map[string]interface{}{ - "keyId": "...", - "privateKey": "...", - "teamId": "...", - }, - }, - }, - }, - }} -} -``` - - - - -```python -from supertokens_python.recipe.thirdparty.provider import ProviderInput, ProviderConfig, ProviderClientConfig - -providers = [ - ProviderInput( - config=ProviderConfig( - third_party_id="apple", - clients=[ - ProviderClientConfig( - client_type="web-and-android", - client_id="...", - additional_config={ - "keyId": "...", - "privateKey": "...", - "teamId": "...", - }, - ), - ProviderClientConfig( - client_type="ios", - client_id="...", - additional_config={ - "keyId": "...", - "privateKey": "...", - "teamId": "...", - }, - ), - ], - ), - ), -] -``` - - - - -For the frontend, you would need to use the right `clientType` as shown below: - - - - - - - - - -We pass in the `clientType` during the init call. - -```tsx -import SuperTokens from 'supertokens-web-js'; - -SuperTokens.init({ - appInfo: { - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}", - appName: "...", - }, - // highlight-next-line - clientType: "web-and-android", - recipeList: [/*...*/], -}); -``` - -If you are using our pre built UI SDK (supertokens-auth-react) as well, you can provide the `clientType` config to it as follows: - -```tsx -import SuperTokens from 'supertokens-auth-react'; - -SuperTokens.init({ - appInfo: { - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - appName: "...", - }, - // highlight-next-line - clientType: "web-and-android", - recipeList: [/*...*/], -}); -``` - - - - - - - - -We pass in the `clientType` during the init call. - -```tsx -import supertokens from "supertokens-web-js-script"; -supertokens.init({ - appInfo: { - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}", - appName: "...", - }, - // highlight-next-line - clientType: "web-and-android", - recipeList: [/*...*/], -}); -``` - - - - - - - - - - -When making calls to the APIs from your mobile app, the request body also takes a `clientType` prop as seen in the above API calls. - - - - - - -## See also - -- [Post sign in / sign up action](../common-customizations/handling-signinup-success) -- [Changing OAuth provider's scope](../common-customizations/signup-form/built-in-providers#setting-oauth-scopes) -- [See all built in providers](../common-customizations/signup-form/built-in-providers) -- [Integrating with a custom OAuth provider](../common-customizations/signup-form/custom-providers) -- [Fetching profile info and using the provider's access token on the backend](../common-customizations/get-user-info) -- [Customising user ID format](../common-customizations/userid-format) diff --git a/v2/thirdpartypasswordless/introduction.mdx b/v2/thirdpartypasswordless/introduction.mdx index dddf52372..1b61d2fbd 100644 --- a/v2/thirdpartypasswordless/introduction.mdx +++ b/v2/thirdpartypasswordless/introduction.mdx @@ -1,48 +1,12 @@ --- id: introduction -title: Introduction & Architecture +title: Introduction hide_title: true +hide_table_of_contents: true --- -# Passwordless with Social / Enterprise (OAuth 2.0, SAML) login +import Redirector from '/src/components/Redirector'; -## Features -- Sign in / up with OTP or / and [magic link via SMS or email](https://supertokens.com/features/email-magic-links)
      -- Sign-up / Sign-in with [OAuth 2.0 / OIDC / SAML providers](https://supertokens.com/features/single-sign-on) (Like Google, Facebook, Active Directory, etc)
      -- Secure session management
      -- Customise email or SMS
      -- Integrate with your own email / SMS sending service
      -- Email verification
      + - - - - -## Demo application - -- See our [live demo app](https://^{docsLinkRecipeName}.demo.supertokens.com/). -- We have a read-only user management dashboard (with fake data), and it can be accessed [on this link](https://dashboard.demo.supertokens.com/api/auth/dashboard). The credentials for logging in are: - ```text - email: demo@supertokens.com - password: abcd1234 - ``` -- Generate a starter app - ```bash - npx create-supertokens-app@latest --recipe=^{docsLinkRecipeName} - ``` -- The user management dashboard for the starter app can be accessed on `http://localhost:3001/auth/dashboard`. You will have to first create users (instrs are on the dashboard UI), before logging in. - -## Architecture - -import Architecture from "../community/reusableMD/intro-architecture.mdx" - - - -## Next steps - -import NextSteps from "../community/reusableMD/intro-next-steps.mdx" - - - - \ No newline at end of file diff --git a/v2/thirdpartypasswordless/pre-built-ui/enable-email-verification.mdx b/v2/thirdpartypasswordless/pre-built-ui/enable-email-verification.mdx index 070f4c321..4f3ade3f4 100644 --- a/v2/thirdpartypasswordless/pre-built-ui/enable-email-verification.mdx +++ b/v2/thirdpartypasswordless/pre-built-ui/enable-email-verification.mdx @@ -4,815 +4,8 @@ title: Enable email verification hide_title: true --- -# Enable email verification +import Redirector from '/src/components/Redirector'; -:::important -- For social / third party logins, the user's email is automatically marked as verified if the user had verified their email to the login provider. -- For passwordless login with email, a user's email is automatically marked as verified when they login. Therefore, the only time this flow would be triggered is if a user changes their email during a session. -::: - - - + -import CustomAdmonition from "/src/components/customAdmonition" -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"; - - -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 - - - - -```tsx -import SuperTokens from "supertokens-node"; -import EmailVerification from "supertokens-node/recipe/emailverification"; -import Session from "supertokens-node/recipe/session"; - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // highlight-start - EmailVerification.init({ - mode: "REQUIRED", // or "OPTIONAL" - }), - // highlight-end - Session.init(), - ], -}); -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/emailverification" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evmodels" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - supertokens.Init(supertokens.TypeInput{ - RecipeList: []supertokens.Recipe{ - // highlight-start - emailverification.Init(evmodels.TypeInput{ - Mode: evmodels.ModeRequired, // or evmodels.ModeOptional - }), - // highlight-end - session.Init(&sessmodels.TypeInput{}), - }, - }) -} -``` - - - - -```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import session -from supertokens_python.recipe import emailverification - -init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore - recipe_list=[ - # highlight-start - emailverification.init(mode='REQUIRED'), # or 'OPTIONAL' - # highlight-end - session.init() - ] -) -``` - - - - - - - - - - -import {Question}from "/src/components/question" - -## Step 2: Frontend setup - - - - - - - -```tsx -import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; -import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui"; -import EmailVerification from "supertokens-auth-react/recipe/emailverification"; -^{prebuiltuiimport} -import { EmailVerificationPreBuiltUI } from "supertokens-auth-react/recipe/emailverification/prebuiltui"; -import Session from "supertokens-auth-react/recipe/session"; -import reactRouterDOM, { Routes, BrowserRouter as Router, Route } from "react-router-dom"; - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // highlight-start - EmailVerification.init({ - mode: "REQUIRED", // or "OPTIONAL" - }), - // highlight-end - Session.init(), - ], -}); - -function App() { - return ( - -
      - -
      - - // highlight-start - {getSuperTokensRoutesForReactRouterDom(reactRouterDOM, [^{recipePreBuiltUINameCapitalLetters}, EmailVerificationPreBuiltUI])} - // highlight-end - // ... other routes - -
      -
      -
      -
      - ); -} -``` - -
      - - - -```tsx -import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; -import { canHandleRoute, getRoutingComponent } from "supertokens-auth-react/ui"; -import EmailVerification from "supertokens-auth-react/recipe/emailverification"; -^{prebuiltuiimport} -import { EmailVerificationPreBuiltUI } from "supertokens-auth-react/recipe/emailverification/prebuiltui"; -import Session from "supertokens-auth-react/recipe/session"; - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // highlight-start - EmailVerification.init({ - mode: "REQUIRED", // or "OPTIONAL" - }), - // highlight-end - Session.init(), - ], -}); - -function App() { - // highlight-start - if (canHandleRoute([^{recipePreBuiltUINameCapitalLetters}, EmailVerificationPreBuiltUI])) { - return getRoutingComponent([^{recipePreBuiltUINameCapitalLetters}, EmailVerificationPreBuiltUI]) - } - // highlight-end - return ( - {/*Your app*/} - ); -} -``` - - - -
      - -
      - - - -```tsx -// this goes in the auth route config of your frontend app (once the pre built UI script has been loaded) -import {init as supertokensUIInit} from "supertokens-auth-react-script"; -import supertokensUIEmailVerification from "supertokens-auth-react-script/recipe/emailverification"; -import supertokensUISession from "supertokens-auth-react-script/recipe/session"; - -supertokensUIInit({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - // highlight-start - supertokensUIEmailVerification.init({ - mode: "REQUIRED", // or "OPTIONAL" - }), - // highlight-end - supertokensUISession.init(), - ], -}); -``` - - - -
      - - - - - - - -## Step 3: Checking if the user's email is verified in your APIs - -

      If using REQUIRED mode

      - -On the backend, when you initialize the email verification recipe in this mode, the `verifySession` middleware automatically checks if the user's email is verified based on the contents of the session's payload. If the email is not verified, the `verifySession` middleware will return a `403` status code to the client. - -

      If using OPTIONAL mode

      - -In this mode, you need to check if the email is verified yourself in the APIs in which you want this constraint. The verification status should already be in the session's payload. - - - - - - -```tsx -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import express from "express"; -import { SessionRequest } from "supertokens-node/framework/express"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let app = express(); - -app.post( - "/update-blog", - verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - }), - async (req: SessionRequest, res) => { - // All validator checks have passed and the user has a verified email address - } -); -``` - - - -```tsx -import Hapi from "@hapi/hapi"; -import { verifySession } from "supertokens-node/recipe/session/framework/hapi"; -import {SessionRequest} from "supertokens-node/framework/hapi"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let server = Hapi.server({ port: 8000 }); - -server.route({ - path: "/update-blog", - method: "post", - options: { - pre: [ - { - method: verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - }), - }, - ], - }, - handler: async (req: SessionRequest, res) => { - // All validator checks have passed and the user has a verified email address - } -}) -``` - - - -```tsx -import Fastify from "fastify"; -import { verifySession } from "supertokens-node/recipe/session/framework/fastify"; -import { SessionRequest } from "supertokens-node/framework/fastify"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let fastify = Fastify(); - -fastify.post("/update-blog", { - preHandler: verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - }), -}, async (req: SessionRequest, res) => { - // All validator checks have passed and the user has a verified email address -}); -``` - - - - -```tsx -import { verifySession } from "supertokens-node/recipe/session/framework/awsLambda"; -import { SessionEvent } from "supertokens-node/framework/awsLambda"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -async function updateBlog(awsEvent: SessionEvent) { - // All validator checks have passed and the user has a verified email address -}; - -exports.handler = verifySession(updateBlog, { - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] -}); -``` - - - - -```tsx -import KoaRouter from "koa-router"; -import { verifySession } from "supertokens-node/recipe/session/framework/koa"; -import {SessionContext} from "supertokens-node/framework/koa"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -let router = new KoaRouter(); - -router.post("/update-blog", verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - }), async (ctx: SessionContext, next) => { - // All validator checks have passed and the user has a verified email address -}); -``` - - - - -```tsx -import { inject, intercept } from "@loopback/core"; -import { RestBindings, MiddlewareContext, post, response } from "@loopback/rest"; -import { verifySession } from "supertokens-node/recipe/session/framework/loopback"; -import Session from "supertokens-node/recipe/session"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -class SetRole { - constructor(@inject(RestBindings.Http.CONTEXT) private ctx: MiddlewareContext) { } - @post("/update-blog") - @intercept(verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - })) - @response(200) - async handler() { - // All validator checks have passed and the user has a verified email address - } -} -``` - - - - -```tsx -import { superTokensNextWrapper } from 'supertokens-node/nextjs' -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import { SessionRequest } from "supertokens-node/framework/express"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -// highlight-start -export default async function setRole(req: SessionRequest, res: any) { - await superTokensNextWrapper( - async (next) => { - await verifySession({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - })(req, res, next); - }, - req, - res - ) - // All validator checks have passed and the user has a verified email address -} -``` - - - - -```tsx -import SuperTokens from "supertokens-node"; -import { NextResponse, NextRequest } from "next/server"; -import { withSession } from "supertokens-node/nextjs"; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; -// @ts-ignore -import { backendConfig } from "@/app/config/backend"; - -SuperTokens.init(backendConfig()); - -export async function POST(request: NextRequest) { - return withSession(request, async (err, session) => { - if (err) { - return NextResponse.json(err, { status: 500 }); - } - // All validator checks have passed and the user has a verified email address - return NextResponse.json({ message: "Your email is verified!" }); - }, - { - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators) => [...globalValidators, EmailVerificationClaim.validators.isVerified()], - } - ); -} -``` - - - - -```tsx -import { Controller, Post, UseGuards, Request, Response, Session } from "@nestjs/common"; -import { SessionContainer, SessionClaimValidator } from "supertokens-node/recipe/session"; -// @ts-ignore -import { AuthGuard } from './auth/auth.guard'; -import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; - -@Controller() -export class ExampleController { - @Post('example') - @UseGuards(new AuthGuard({ - // highlight-next-line - overrideGlobalClaimValidators: async (globalValidators: SessionClaimValidator[]) => [...globalValidators, EmailVerificationClaim.validators.isVerified()] - })) - async postExample(@Session() session: SessionContainer): Promise { - // All validator checks have passed and the user has a verified email address - return true; - } -} -``` - - - - - - - - -```go -import ( - "net/http" - - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - _ = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - session.VerifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }, exampleAPI).ServeHTTP(rw, r) - }) -} - -func exampleAPI(w http.ResponseWriter, r *http.Request) { - // TODO: session is verified and all validators have passed.. -} -``` - - - - -```go -import ( - "net/http" - - "github.com/gin-gonic/gin" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - router := gin.New() - - // Wrap the API handler in session.VerifySession - router.POST("/likecomment", verifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }), exampleAPI) -} - -// This is a function that wraps the supertokens verification function -// to work the gin -func verifySession(options *sessmodels.VerifySessionOptions) gin.HandlerFunc { - return func(c *gin.Context) { - session.VerifySession(options, func(rw http.ResponseWriter, r *http.Request) { - c.Request = c.Request.WithContext(r.Context()) - c.Next() - })(c.Writer, c.Request) - // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly - c.Abort() - } -} - -func exampleAPI(c *gin.Context) { - // TODO: session is verified and all claim validators pass. -} -``` - - - - -```go -import ( - "net/http" - - "github.com/go-chi/chi" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - r := chi.NewRouter() - - // Wrap the API handler in session.VerifySession - r.Post("/likecomment", session.VerifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }, exampleAPI)) -} - -func exampleAPI(w http.ResponseWriter, r *http.Request) { - // TODO: session is verified and all claim validators pass. -} -``` - - - - -```go -import ( - "net/http" - - "github.com/gorilla/mux" - "github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/claims" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - router := mux.NewRouter() - - // Wrap the API handler in session.VerifySession - router.HandleFunc("/likecomment", session.VerifySession(&sessmodels.VerifySessionOptions{ - // highlight-start - OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { - globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil)) - return globalClaimValidators, nil - }, - // highlight-end - }, exampleAPI)).Methods(http.MethodPost) -} - -func exampleAPI(w http.ResponseWriter, r *http.Request) { - // TODO: session is verified and all claim validators pass. -} -``` - - - - - - - - - -```python -from supertokens_python.recipe.session.framework.fastapi import verify_session -from supertokens_python.recipe.emailverification import EmailVerificationClaim -from supertokens_python.recipe.session import SessionContainer -from fastapi import Depends - -@app.post('/like_comment') # type: ignore -async def like_comment(session: SessionContainer = Depends( - verify_session( - # highlight-start - # We add the EmailVerificationClaim's is_verified validator - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \ - [EmailVerificationClaim.validators.is_verified()] - # highlight-end - ) -)): - # All validator checks have passed and the user has a verified email address - pass -``` - - - - -```python -from supertokens_python.recipe.session.framework.flask import verify_session -from supertokens_python.recipe.emailverification import EmailVerificationClaim - -@app.route('/update-jwt', methods=['POST']) # type: ignore -@verify_session( - # highlight-start - # We add the EmailVerificationClaim's is_verified validator - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \ - [EmailVerificationClaim.validators.is_verified()] - # highlight-end -) -def like_comment(): - # All validator checks have passed and the user has a verified email address - pass -``` - - - - -```python -from supertokens_python.recipe.session.framework.django.asyncio import verify_session -from django.http import HttpRequest -from supertokens_python.recipe.emailverification import EmailVerificationClaim - -@verify_session( - # highlight-start - # We add the EmailVerificationClaim's is_verified validator - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \ - [EmailVerificationClaim.validators.is_verified()] - # highlight-end -) -async def like_comment(request: HttpRequest): - # All validator checks have passed and the user has a verified email address - pass -``` - - - - - - - -We add the SDK's `EmailVerificationClaim` validator to the `verifySession` middleware call as shown above, and that will only allow access if the email is verified, else it will return `403` to the frontend. - - - - - - - -## Step 4: Protecting frontend routes - - - - -

      If using REQUIRED mode

      - -Wrapping your website routes using `` should enforce email verification. If the user's email is not verified, SuperTokens will automatically redirect the user to the email verification screen. - -

      If using OPTIONAL mode

      - -```tsx -import React from "react"; -import { SessionAuth, useSessionContext } from 'supertokens-auth-react/recipe/session'; -import { EmailVerificationClaim } from 'supertokens-auth-react/recipe/emailverification'; - -const VerifiedRoute = (props: React.PropsWithChildren) => { - return ( - - - {props.children} - - - ); -} - -function InvalidClaimHandler(props: React.PropsWithChildren) { - let sessionContext = useSessionContext(); - if (sessionContext.loading) { - return null; - } - - if (sessionContext.invalidClaims.some(i => i.id === EmailVerificationClaim.id)) { - // Alternatively you could redirect the user to the email verification screen to trigger the verification email - // Note: /auth/verify-email is the default email verification path - // window.location.assign("/auth/verify-email") - return
      You cannot access this page because your email address is not verified.
      - } - - // We show the protected route since all claims validators have - // passed implying that the user has verified their email. - return
      {props.children}
      ; -} -``` -Above we are creating a generic component called `VerifiedRoute` which enforces that its child components can only be rendered if the user has a verified email address. - -In the `VerifiedRoute` component, we use the `SessionAuth` wrapper to ensure that the session exists. The `SessionAuth` wrapper will create a context that contains a prop called `invalidClaims` which will contain a list of all claim validations that have failed. - -The email verification recipe on the frontend, adds the `EmailVerificationClaim` validator automatically, so if the user's email is not verified, the `invalidClaims` prop will contain information about that. Alternatively you could also redirect the user to the default email verification path to trigger the sending of the verification email. - -We check the result of the validation in the `InvalidClaimHandler` component which displays `"You cannot access this page because your email address is not verified."` if the `EmailVerificationClaim` validator failed. - -If all validations pass, we render the `props.children` component. - -
      - - - -```tsx -import Session from "supertokens-web-js/recipe/session"; -import { EmailVerificationClaim } from "supertokens-web-js/recipe/emailverification"; - -async function shouldLoadRoute(): Promise { - if (await Session.doesSessionExist()) { - // highlight-start - let validationErrors = await Session.validateClaims(); - - if (validationErrors.length === 0) { - // user has verified their email address - return true; - } else { - for (const err of validationErrors) { - if (err.id === EmailVerificationClaim.id) { - // email is not verified - } - } - } - // highlight-end - } - // a session does not exist, or email is not verified - return false -} -``` - -In your protected routes, you need to first check if a session exists, and then call the `Session.validateClaims` function as shown above. This function inspects the session's contents and runs claim validators on them. If a claim validator fails, it will be reflected in the `validationErrors` variable. The `EmailVerificationClaim` validator will be automatically checked by this function since you have initialized the email verification recipe. - - - -
      - - - - - - - -## See also - -- [Post email verification action](../common-customizations/email-verification/handling-email-verification-success) -- [Change email verification link's lifetime](../common-customizations/email-verification/changing-token-lifetime) -- [Customise email template or email delivery method](../email-delivery/about) -- [Manually changing email verification status for a user](../common-customizations/email-verification/changing-email-verification-status) -- [Generating email verification links manually](../common-customizations/email-verification/generate-link-manually) -- [Replacing, customising or embedding the frontend UI](../common-customizations/email-verification/embed-in-page) -- 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/thirdpartypasswordless/pre-built-ui/handling-session-tokens.mdx b/v2/thirdpartypasswordless/pre-built-ui/handling-session-tokens.mdx index 69b80421f..6ff934e75 100644 --- a/v2/thirdpartypasswordless/pre-built-ui/handling-session-tokens.mdx +++ b/v2/thirdpartypasswordless/pre-built-ui/handling-session-tokens.mdx @@ -4,165 +4,7 @@ title: Handling session tokens hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -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'; + -# Handling session tokens - - - - - -:::success -No action required. -::: - -Our frontend SDK handles everything for you. You only need to make sure that you have called `supertokens.init` before making any network requests. - -Our SDK adds interceptors to `fetch` and `XHR` (used by `axios`) to save and add session tokens from and to the request. - -By default, our web SDKs use cookies to provide credentials. - - - - - -## Getting the access token - -:::caution -Our SDK automatically handles adding the access token to request headers. You only need to add the access token to the request if you want to send the access token to a different API domain than what is configured on the frontend SDK init function call. -::: - -If you are using a header-based session or enabled `exposeAccessTokenToFrontendInCookieBasedAuth` (see below), you can read the access token on the frontend using the `getAccessToken` method: - - - - - - - - -```tsx -import Session from "supertokens-auth-react/recipe/session"; - -async function getToken(): Promise { - // highlight-next-line - const accessToken = await Session.getAccessToken(); - console.log(accessToken); -} -``` - - - - - -```tsx -import Session from "supertokens-web-js/recipe/session"; - -async function getToken(): Promise { - // highlight-next-line - const accessToken = await Session.getAccessToken(); - console.log(accessToken); -} -``` - - - - - - - -### If using cookie-based sessions - -:::caution -This will expose the access token to the frontend, meaning it could be vulnerable to XSS attacks. -::: - -:::important -If you are using header-based sessions, you can skip this step -::: - -By enabling this setting, you'll expose the access token to your frontend code even if you use cookies for authentication. - - - - - - - - -```tsx -import SuperTokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; - -SuperTokens.init({ - supertokens: { - connectionURI: "..." - }, - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "..." - }, - recipeList: [ - Session.init({ - //highlight-start - exposeAccessTokenToFrontendInCookieBasedAuth: true, - //highlight-end - }) - ] -}); -``` - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - supertokens.Init(supertokens.TypeInput{ - RecipeList: []supertokens.Recipe{ - session.Init(&sessmodels.TypeInput{ - //highlight-start - ExposeAccessTokenToFrontendInCookieBasedAuth: true, - //highlight-end - }), - }, - }) -} -``` - - - -```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import session - -init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore - recipe_list=[ - session.init( - # highlight-next-line - expose_access_token_to_frontend_in_cookie_based_auth=True - ) - ] -) -``` - - - - - diff --git a/v2/thirdpartypasswordless/pre-built-ui/multitenant-login.mdx b/v2/thirdpartypasswordless/pre-built-ui/multitenant-login.mdx index aa9376fc6..bf2091f6c 100644 --- a/v2/thirdpartypasswordless/pre-built-ui/multitenant-login.mdx +++ b/v2/thirdpartypasswordless/pre-built-ui/multitenant-login.mdx @@ -4,456 +4,8 @@ title: "Multitenant and Enterprise SSO login" hide_title: true --- -import AppInfoForm from "/src/components/appInfoForm" -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import PythonSyncAsyncSubTabs from "/src/components/tabs/PythonSyncAsyncSubTabs"; -import CoreVersionSubTabs from "/src/components/tabs/CoreVersionSubTabs"; -import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs"; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import MultiTenancyPaidBanner from '../../community/reusableMD/multitenancy/MultiTenancyPaidBanner.mdx' -import CoreInjector from "/src/components/coreInjector" -import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" +import Redirector from '/src/components/Redirector'; - - - - + -# Multitenant and Enterprise SSO login - -Multitenant login is a feature that lets you customize the login experience for each of your customers. For example, a customer `customer1` hosted on `customer1.yourdomain.com` can have login with `Active Directory` and `Google`, and another customer `customer2` hosted on `customer2.yourdomain.com` can have login with `Okta`, `Facebook` and magic link based login. - -This is also the page that you should see if you want to implement sign in with: -- Okta (`thirdPartyId: "okta"`) -- SAML (`thirdPartyId: "boxy-saml"`) -- Active Directory (`thirdPartyId: "active-directory"`) -- Google Workspaces (`thirdPartyId: "google-workspaces"`) -- GitLab (`thirdPartyId: "gitlab"`) -- Bitbucket (`thirdPartyId: "bitbucket"`) -- Or any other workforce IdP - - - - - - - -## Step 1: Create and configure a new tenant in SuperTokens core - -Each tenant can be configured with a unique `tenantId`, and the list of third party connections (Active Directory, Google etc..) that should be allowed for them. - -You can create a new tenant using our backend SDKs or via a `cURL` command to the core. - - - - - - - - - - - -```tsx -import Multitenancy from "supertokens-node/recipe/multitenancy"; -import { FactorIds } from "supertokens-node/recipe/multifactorauth"; - -async function createNewTenant() { - - let resp = await Multitenancy.createOrUpdateTenant("customer1", { - firstFactors: [FactorIds.OTP_PHONE, FactorIds.OTP_EMAIL, FactorIds.LINK_PHONE, FactorIds.LINK_EMAIL, FactorIds.THIRDPARTY] - }); - - if (resp.createdNew) { - // new tenant was created - } else { - // existing tenant's config was modified. - } -} -``` - -In the example above, we have added all factor IDs related to the passwordless recipe. However, you can choose to use a subset of them. The factor IDs are described below: -- ThirdParty: `FactorIds.THIRDPARTY` -- Passwordless: - - With email OTP: `FactorIds.OTP_EMAIL` - - With SMS OTP: `FactorIds.OTP_PHONE` - - With email magic link: `FactorIds.LINK_EMAIL` - - With SMS magic link: `FactorIds.LINK_PHONE` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/multitenancy" - "github.com/supertokens/supertokens-golang/recipe/multitenancy/multitenancymodels" -) - -func main() { - tenantId := "customer1" - thirdPartyEnabled := true - passwordlessEnabled := true - - // highlight-start - resp, err := multitenancy.CreateOrUpdateTenant(tenantId, multitenancymodels.TenantConfig{ - ThirdPartyEnabled: &thirdPartyEnabled, - PasswordlessEnabled: &passwordlessEnabled, - }) - // highlight-end - - if err != nil { - // handle error - } - if resp.OK.CreatedNew { - // new tenant was created - } else { - // existing tenant's config was modified. - } -} -``` - - - - - - - -```python -from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate -from supertokens_python.recipe.multifactorauth.types import FactorIds - - -async def some_func(): - response = await create_or_update_tenant( - "customer1", - TenantConfigCreateOrUpdate( - first_factors=[ - FactorIds.OTP_PHONE, - FactorIds.OTP_EMAIL, - FactorIds.LINK_PHONE, - FactorIds.LINK_EMAIL, - FactorIds.THIRDPARTY, - ] - ), - ) - - if response.status != "OK": - print("Handle error") - elif response.created_new: - print("new tenant was created") - else: - print("existing tenant's config was modified.") -``` - - - - - -```python -from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate -from supertokens_python.recipe.multifactorauth.types import FactorIds - - -def some_func(): - response = create_or_update_tenant( - "customer1", - TenantConfigCreateOrUpdate( - first_factors=[ - FactorIds.OTP_PHONE, - FactorIds.OTP_EMAIL, - FactorIds.LINK_PHONE, - FactorIds.LINK_EMAIL, - FactorIds.THIRDPARTY, - ] - ), - ) - - if response.status != "OK": - print("Handle error") - elif response.created_new: - print("new tenant was created") - else: - print("existing tenant's config was modified.") -``` - - - - - - - - - - - - - - -```bash -curl --location --request PUT '^{coreInjector_uri_without_quotes}/recipe/multitenancy/tenant/v2' \ ---header 'api-key: ^{coreInjector_api_key_without_quotes}' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "tenantId": "customer1", - "firstFactors": ["otp-email", "otp-phone", "link-email", "link-phone", "thirdparty"] -}' -``` - -In the example above, we have added all factor IDs related to the passwordless recipe. However, you can choose to use a subset of them. The factor IDs are described below: -- ThirdParty: `thirdparty` -- Passwordless: - - With email OTP: `otp-email` - - With SMS OTP: `otp-phone` - - With email magic link: `link-email` - - With SMS magic link: `link-phone` - - - - - -```bash -curl --location --request PUT '^{coreInjector_uri_without_quotes}/recipe/multitenancy/tenant' \ ---header 'api-key: ^{coreInjector_api_key_without_quotes}' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "tenantId": "customer1", - "thirdPartyEnabled": true, - "passwordlessEnabled": true -}' -``` - - - - - - - - - - - - -Create Tenant - - - - - - - - - - - -Once a tenant is created, add their thirdparty providers as shown below. - - - - - - - - - - - -```tsx -import Multiteancy from "supertokens-node/recipe/multitenancy"; - -async function createTenant() { - let resp = await Multiteancy.createOrUpdateThirdPartyConfig("customer1", { - thirdPartyId: "active-directory", - name: "Active Directory", - clients: [{ - clientId: "...", - clientSecret: "...", - }], - oidcDiscoveryEndpoint: "https://login.microsoftonline.com//v2.0/.well-known/openid-configuration", - }); - - if (resp.createdNew) { - // Active Directory added to customer1 - } else { - // Existing active directory config overwritten for customer1 - } -} -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/multitenancy" - "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" -) - -func main() { - tenantId := "customer1" - - // highlight-start - resp, err := multitenancy.CreateOrUpdateThirdPartyConfig(tenantId, tpmodels.ProviderConfig{ - ThirdPartyId: "active-directory", - Name: "Active Directory", - Clients: []tpmodels.ProviderClientConfig{ - { - ClientID: "...", - ClientSecret: "...", - }, - }, - OIDCDiscoveryEndpoint: "https://login.microsoftonline.com//v2.0/.well-known/openid-configuration", - }, nil, nil) - // highlight-end - - if err != nil { - // handle error - } - if resp.OK.CreatedNew { - // Active Directory added to customer1 - } else { - // Existing active directory config overwritten for customer1 - } -} -``` - - - - - - - -```python -from supertokens_python.recipe.multitenancy.asyncio import create_or_update_third_party_config -from supertokens_python.recipe.thirdparty.provider import ProviderConfig, ProviderClientConfig - -async def update_tenant(): - result = await create_or_update_third_party_config( - "customer1", - config=ProviderConfig( - third_party_id="active-directory", - name="Active Directory", - clients=[ - ProviderClientConfig( - client_id="...", - client_secret="...", - ) - ], - oidc_discovery_endpoint="https://login.microsoftonline.com//v2.0/.well-known/openid-configuration", - ), - ) - - if result.status != "OK": - print("Error adding active directory to tenant") - elif result.created_new: - print("Active directory was added to the tenant") - else: - print("Existing tenant's active directory config was modified") -``` - - - - - -```python -from supertokens_python.recipe.multitenancy.syncio import create_or_update_third_party_config -from supertokens_python.recipe.thirdparty.provider import ProviderConfig, ProviderClientConfig - -result = create_or_update_third_party_config( - "customer1", - config=ProviderConfig( - third_party_id="active-directory", - name="Active Directory", - clients=[ - ProviderClientConfig( - client_id="...", - client_secret="...", - ) - ] - ), - ) - -if result.status != "OK": - print("Error creating or updating tenant") -elif result.created_new: - print("New tenant was created") -else: - print("Existing tenant's config was modified") -``` - - - - - - - - - - -```bash -curl --location --request PUT '^{coreInjector_uri_without_quotes}//recipe/multitenancy/config/thirdparty' \ ---header 'api-key: ^{coreInjector_api_key_without_quotes}' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "config": { - "thirdPartyId": "active-directory", - "name": "Active Directory", - "clients": [ - { - "clientId": "...", - "clientSecret": "..." - } - ], - "oidcDiscoveryEndpoint": "https://login.microsoftonline.com//v2.0/.well-known/openid-configuration" - } -}' -``` - - - - - - - -Create Third Party Provider - - - - - - - - - - - -:::important -The above shows how to add an Active Directory config for the `customer1` tenant. You can see the config structure for all the in built providers [on this page](../common-customizations/sign-in-and-up/provider-config). -::: - - - - - - - -## Step 2: Build your multi tenant a UX flow - -The most common multi tenant flows are: -- [Tenants use a common domain to login](../common-customizations/multi-tenancy/common-domain-login): All tenants login using the same page (for example, `example.com/auth`) and are optionally redirected to their sub domain post login. At the start of the login flow, the customer will have to input their tenantId / workspace URL / identifier - as defined by you, and the login methods shown would be based on their tenantId. -- [Tenants use their sub domain to login](../common-customizations/multi-tenancy/sub-domain-login): Here, each tenant has a sub domain assigned to them (for example `customer1.example.com`, `customer2.example.com`, ...), and they would visit their sub domain to login and access their app. Each sub domain's login experience may be different (as defined by you or the tenant). - -SuperTokens is flexible enough to allow other forms of UX as well, but since the above two flow are most common, we provide dedicated docs for them (see the links above). - -## See also - -- Multi tenant [single domain](../common-customizations/multi-tenancy/common-domain-login) or [sub domain](../common-customizations/multi-tenancy/sub-domain-login) login. -- [See all built in multitenant providers](../common-customizations/sign-in-and-up/provider-config). -- [See how to add custom multitenant provider](../common-customizations/multi-tenancy/custom-provider). -- [SAML login](../common-customizations/saml/what-is-saml). -- [Tenant data isolation](../common-customizations/multi-tenancy/new-tenant-config#providing-additional-configuration-per-tenant). - - diff --git a/v2/thirdpartypasswordless/pre-built-ui/securing-routes.mdx b/v2/thirdpartypasswordless/pre-built-ui/securing-routes.mdx index a45ea028c..adf124a95 100644 --- a/v2/thirdpartypasswordless/pre-built-ui/securing-routes.mdx +++ b/v2/thirdpartypasswordless/pre-built-ui/securing-routes.mdx @@ -4,530 +4,8 @@ title: "Securing your API and frontend routes" hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -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 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 + -## Protecting APIs - - - - - - - -### Requiring an active session - -For your APIs that require a user to be logged in, use the `verifySession` middleware - - - - - - -```tsx -import express from "express"; -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import { SessionRequest } from "supertokens-node/framework/express"; - -let app = express(); - -// highlight-start -app.post("/like-comment", verifySession(), (req: SessionRequest, res) => { - let userId = req.session!.getUserId(); - // highlight-end - //.... -}); -``` - - - - -```tsx -import Hapi from "@hapi/hapi"; -import { verifySession } from "supertokens-node/recipe/session/framework/hapi"; -import { SessionRequest } from "supertokens-node/framework/hapi"; - -let server = Hapi.server({ port: 8000 }); - -server.route({ - path: "/like-comment", - method: "post", - //highlight-start - options: { - pre: [ - { - method: verifySession() - }, - ], - }, - handler: async (req: SessionRequest, res) => { - let userId = req.session!.getUserId(); - //highlight-end - //... - } -}) -``` - - - -```tsx -import Fastify from "fastify"; -import { verifySession } from "supertokens-node/recipe/session/framework/fastify"; -import { SessionRequest } from "supertokens-node/framework/fastify"; - -let fastify = Fastify(); - -//highlight-start -fastify.post("/like-comment", { - preHandler: verifySession(), -}, (req: SessionRequest, res) => { - let userId = req.session!.getUserId(); - //highlight-end - //.... -}); -``` - - - - -```tsx -import { verifySession } from "supertokens-node/recipe/session/framework/awsLambda"; -import { SessionEventV2 } from "supertokens-node/framework/awsLambda"; - -async function likeComment(awsEvent: SessionEventV2) { - let userId = awsEvent.session!.getUserId(); - //.... -}; - -//highlight-next-line -exports.handler = verifySession(likeComment); -``` - - - - -```tsx -import KoaRouter from "koa-router"; -import { verifySession } from "supertokens-node/recipe/session/framework/koa"; -import { SessionContext } from "supertokens-node/framework/koa"; - -let router = new KoaRouter(); - -//highlight-start -router.post("/like-comment", verifySession(), (ctx: SessionContext, next) => { - let userId = ctx.session!.getUserId(); - //highlight-end - //.... -}); -``` - - - - -```tsx -import { inject, intercept } from "@loopback/core"; -import { RestBindings, MiddlewareContext, post, response } from "@loopback/rest"; -import { verifySession } from "supertokens-node/recipe/session/framework/loopback"; -import { SessionContext } from "supertokens-node/framework/loopback"; - -class LikeComment { - //highlight-start - constructor(@inject(RestBindings.Http.CONTEXT) private ctx: MiddlewareContext) { } - @post("/like-comment") - @intercept(verifySession()) - @response(200) - handler() { - let userId = (this.ctx as SessionContext).session!.getUserId(); - //highlight-end - //.... - } -} -``` - - - - -```tsx -import { superTokensNextWrapper } from 'supertokens-node/nextjs' -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import { SessionRequest } from "supertokens-node/framework/express"; - -// highlight-start -export default async function likeComment(req: SessionRequest, res: any) { - await superTokensNextWrapper( - async (next) => { - await verifySession()(req, res, next); - }, - req, - res - ) - - let userId = req.session!.getUserId(); - // highlight-end - //.... -} -``` - - - - -```tsx -import { NextResponse, NextRequest } from "next/server"; -import SuperTokens from "supertokens-node"; -import { withSession } from "supertokens-node/nextjs"; -// @ts-ignore -import { backendConfig } from "@/app/config/backend"; - -SuperTokens.init(backendConfig()); - -export function POST(request: NextRequest) { - return withSession(request, async (err, session) => { - if (err) { - return NextResponse.json(err, { status: 500 }); - } - const userId = session!.getUserId(); - //.... - return NextResponse.json({}); - }); -} -``` - - - - -```tsx -import { Controller, Post, UseGuards, Session } from "@nestjs/common"; -import { SessionContainer } from "supertokens-node/recipe/session"; -// @ts-ignore -import { AuthGuard } from './auth/auth.guard'; - -@Controller() -export class ExampleController { - @Post('example') - @UseGuards(new AuthGuard()) // For more information about this guard please read our NestJS guide. - async postExample(@Session() session: SessionContainer): Promise { - //highlight-start - let userId = session.getUserId(); - - //highlight-end - //.... - return true; - } -} -``` - - - - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/supertokens/supertokens-golang/recipe/session" -) - -func main() { - _ = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - // Wrap the API handler in session.VerifySession - session.VerifySession(nil, likeCommentAPI).ServeHTTP(rw, r) - }) -} - -func likeCommentAPI(w http.ResponseWriter, r *http.Request) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(r.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/gin-gonic/gin" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" -) - -func main() { - router := gin.New() - - // Wrap the API handler in session.VerifySession - router.POST("/likecomment", verifySession(nil), likeCommentAPI) -} - -// This is a function that wraps the supertokens verification function -// to work the gin -func verifySession(options *sessmodels.VerifySessionOptions) gin.HandlerFunc { - return func(c *gin.Context) { - session.VerifySession(options, func(rw http.ResponseWriter, r *http.Request) { - c.Request = c.Request.WithContext(r.Context()) - c.Next() - })(c.Writer, c.Request) - // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly - c.Abort() - } -} - -func likeCommentAPI(c *gin.Context) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(c.Request.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/go-chi/chi" - "github.com/supertokens/supertokens-golang/recipe/session" -) - -func main() { - r := chi.NewRouter() - - // Wrap the API handler in session.VerifySession - r.Post("/likecomment", session.VerifySession(nil, likeCommentAPI)) -} - -func likeCommentAPI(w http.ResponseWriter, r *http.Request) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(r.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - -```go -import ( - "fmt" - "net/http" - - "github.com/gorilla/mux" - "github.com/supertokens/supertokens-golang/recipe/session" -) - -func main() { - router := mux.NewRouter() - - // Wrap the API handler in session.VerifySession - router.HandleFunc("/likecomment", session.VerifySession(nil, likeCommentAPI)).Methods(http.MethodPost) -} - -func likeCommentAPI(w http.ResponseWriter, r *http.Request) { - // retrieve the session object as shown below - sessionContainer := session.GetSessionFromRequestContext(r.Context()) - - userID := sessionContainer.GetUserID() - - fmt.Println(userID) -} -``` - - - - - - - - - -```python -from supertokens_python.recipe.session.framework.fastapi import verify_session -from supertokens_python.recipe.session import SessionContainer -from fastapi import Depends - -# highlight-start -@app.post('/like_comment') # type: ignore -async def like_comment(session: SessionContainer = Depends(verify_session())): - user_id = session.get_user_id() - # highlight-end - - print(user_id) -``` - - - - -```python -from supertokens_python.recipe.session.framework.flask import verify_session -from supertokens_python.recipe.session import SessionContainer -from flask import g - -# highlight-start -@app.route('/update-jwt', methods=['POST']) # type: ignore -@verify_session() -def like_comment(): - session: SessionContainer = g.supertokens # type: ignore - - user_id = session.get_user_id() - # highlight-end - - print(user_id) -``` - - - - -```python -from supertokens_python.recipe.session.framework.django.asyncio import verify_session -from django.http import HttpRequest -from supertokens_python.recipe.session import SessionContainer - -# highlight-start -@verify_session() -async def like_comment(request: HttpRequest): - session: SessionContainer = request.supertokens # type: ignore - - user_id = session.get_user_id() - # highlight-end - - print(user_id) -``` - - - - - - - -The `verifySession` function returns a `401` to the frontend if a session doesn't exist, or if the access token has expired, in which case, our frontend SDK automatically refreshes the session. - -In case of successful session verification, you get access to a `session` object using which you can get the user's ID, or manipulate the session information. - - - - - - - -### Microservice authentication - -For authentication between microservices on your backend, checkout the [microservice auth guide](/docs/microservice_auth/client-credentials). - - - -## Protecting website routes - - - - - -:::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. - -::: - - - - -You can wrap your components with the `` react component. This will ensure that your component renders only if the user is logged in. If they are not logged in, the user will be redirected to the login page. - -```tsx -import React from "react"; -import { - BrowserRouter, - Routes, - Route, -} from "react-router-dom"; -// highlight-next-line -import { SessionAuth } from "supertokens-auth-react/recipe/session"; -// @ts-ignore -import MyDashboardComponent from "./dashboard"; - -class App extends React.Component { - render() { - return ( - - - - {/*Components that require to be protected by authentication*/} - - - // highlight-end - } /> - - - ); - } -} -``` - - - - - -You can use the `doesSessionExist` function to check if a session exists in all your routes. - -```tsx -import Session from 'supertokens-web-js/recipe/session'; - -async function doesSessionExist() { - if (await Session.doesSessionExist()) { - // user is logged in - } else { - // user has not logged in yet - } -} -``` - - - - - - - - - - - -## See also - -- Optional sessions [for APIs](../common-customizations/sessions/session-verification-in-api/verify-session#optional-session-verification) and [the frontend](../common-customizations/sessions/checking-session-front-end) -- [Verifying session without using a middleware](../common-customizations/sessions/session-verification-in-api/get-session) -- Session claim validation [for APIs](../common-customizations/sessions/claims/claim-validators) and [the frontend](../common-customizations/sessions/claims/claim-validators) -- [Changing session lifetime](../common-customizations/sessions/change-session-timeout) -- [Sharing session across sub domains](../common-customizations/sessions/share-sessions-across-sub-domains) - - diff --git a/v2/thirdpartypasswordless/pre-built-ui/setup/backend.mdx b/v2/thirdpartypasswordless/pre-built-ui/setup/backend.mdx index a97984426..577fc5a32 100644 --- a/v2/thirdpartypasswordless/pre-built-ui/setup/backend.mdx +++ b/v2/thirdpartypasswordless/pre-built-ui/setup/backend.mdx @@ -4,1232 +4,8 @@ title: "Step 2: Backend" hide_title: true --- - - +import Redirector from '/src/components/Redirector'; -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import TabItem from '@theme/TabItem'; -import NodeJSFrameworkSubTabsServerless from "/src/components/tabs/NodeJSFrameworkSubTabsServerless" -import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs" -import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" -import AppInfoForm from "/src/components/appInfoForm" -import { PasswordlessBackendForm } from "/src/components/snippetConfigForm/passwordlessBackendForm"; -import CoreInjector from "/src/components/coreInjector" -import { Question, Answer }from "/src/components/question" -import BackendSDKCasing from "/src/components/BackendSDKCasing" -import BackendDeliveryMethod from "../../../passwordless/reusableMD/backendDeliveryMethod.mdx" -# Backend Integration + -## Supported frameworks - -import TechStackSupport from "../../../community/reusableMD/supported-tech-stacks-backend.mdx" - - - -## 1) Install - - - - -```bash -npm i -s supertokens-node -``` - - - - -```bash -go get github.com/supertokens/supertokens-golang -``` - - - - -```bash -pip install supertokens-python -``` - - - - -## 2) Initialise SuperTokens - - - - - - -Add the code below to your server's init file. - - - - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import ThirdParty from "supertokens-node/recipe/thirdparty" -import Passwordless from "supertokens-node/recipe/passwordless" - -supertokens.init({ - framework: "express", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - Passwordless.init({ - flowType: "^{form_flowType}", - contactMethod: "^{form_contactMethod}" - }), - ThirdParty.init(/*TODO: See next steps for third party provider setup */), - Session.init() // initializes session features - ] -}); -``` - - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import ThirdParty from "supertokens-node/recipe/thirdparty" -import Passwordless from "supertokens-node/recipe/passwordless" - -supertokens.init({ - framework: "hapi", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - Passwordless.init({ - flowType: "^{form_flowType}", - contactMethod: "^{form_contactMethod}" - }), - ThirdParty.init(/*TODO: See next steps for third party provider setup */), - Session.init() // initializes session features - ] -}); -``` - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import ThirdParty from "supertokens-node/recipe/thirdparty" -import Passwordless from "supertokens-node/recipe/passwordless" - -supertokens.init({ - framework: "fastify", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - Passwordless.init({ - flowType: "^{form_flowType}", - contactMethod: "^{form_contactMethod}" - }), - ThirdParty.init(/*TODO: See next steps for third party provider setup */), - Session.init() // initializes session features - ] -}); -``` - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import ThirdParty from "supertokens-node/recipe/thirdparty" -import Passwordless from "supertokens-node/recipe/passwordless" - -supertokens.init({ - framework: "koa", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - Passwordless.init({ - flowType: "^{form_flowType}", - contactMethod: "^{form_contactMethod}" - }), - ThirdParty.init(/*TODO: See next steps for third party provider setup */), - Session.init() // initializes session features - ] -}); -``` - - - - -```tsx -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import ThirdParty from "supertokens-node/recipe/thirdparty" -import Passwordless from "supertokens-node/recipe/passwordless" - -supertokens.init({ - framework: "loopback", - supertokens: { - ^{coreInjector_connection_uri_comment} - connectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, - }, - appInfo: { - // learn more about this on https://supertokens.com/docs/session/appinfo - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - Passwordless.init({ - flowType: "^{form_flowType}", - contactMethod: "^{form_contactMethod}" - }), - ThirdParty.init(/*TODO: See next steps for third party provider setup */), - Session.init() // initializes session features - ] -}); -``` - - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/passwordless" - "github.com/supertokens/supertokens-golang/recipe/passwordless/plessmodels" - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/thirdparty" - "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - apiBasePath := "^{form_apiBasePath}" - websiteBasePath := "^{form_websiteBasePath}" - err := supertokens.Init(supertokens.TypeInput{ - Supertokens: &supertokens.ConnectionInfo{ - ^{coreInjector_connection_uri_comment} - ConnectionURI: ^{coreInjector_uri} - ^{coreInjector_api_key_commented}APIKey: ^{coreInjector_api_key}, - }, - AppInfo: supertokens.AppInfo{ - AppName: "^{form_appName}", - APIDomain: "^{form_apiDomain}", - WebsiteDomain: "^{form_websiteDomain}", - APIBasePath: &apiBasePath, - WebsiteBasePath: &websiteBasePath, - }, - RecipeList: []supertokens.Recipe{ - passwordless.Init(plessmodels.TypeInput{ - FlowType: "^{form_flowType}", - ^{form_contactMethod_sendCB_Go} - }), - thirdparty.Init(&tpmodels.TypeInput{ - SignInAndUpFeature: tpmodels.TypeInputSignInAndUp{ - /*TODO: See next steps for third party provider setup */ - }, - }), - session.Init(nil), // initializes session features - }, - }) - - if err != nil { - panic(err.Error()) - } -} -``` - - - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig -from supertokens_python.recipe import thirdparty, passwordless, session - -^{form_contactMethod_import_Python} -init( - app_info=InputAppInfo( - app_name="^{form_appName}", - api_domain="^{form_apiDomain}", - website_domain="^{form_websiteDomain}", - api_base_path="^{form_apiBasePath}", - website_base_path="^{form_websiteBasePath}" - ), - supertokens_config=SupertokensConfig( - ^{coreInjector_connection_uri_comment_with_hash} - connection_uri=^{coreInjector_uri} - ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} - ), - framework='fastapi', - recipe_list=[ - session.init(), # initializes session features - thirdparty.init( - # TODO: See next steps for third party provider setup - ), - passwordless.init( - flow_type="^{form_flowType}", - contact_config=^{form_contactMethod_initialize_Python}() - ) - ], - mode='asgi' # use wsgi if you are running using gunicorn -) -``` - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig -from supertokens_python.recipe import thirdparty, passwordless, session - -^{form_contactMethod_import_Python} -init( - app_info=InputAppInfo( - app_name="^{form_appName}", - api_domain="^{form_apiDomain}", - website_domain="^{form_websiteDomain}", - api_base_path="^{form_apiBasePath}", - website_base_path="^{form_websiteBasePath}" - ), - supertokens_config=SupertokensConfig( - ^{coreInjector_connection_uri_comment_with_hash} - connection_uri=^{coreInjector_uri} - ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} - ), - framework='flask', - recipe_list=[ - session.init(), # initializes session features - thirdparty.init( - # TODO: See next steps for third party provider setup - ), - passwordless.init( - flow_type="^{form_flowType}", - contact_config=^{form_contactMethod_initialize_Python}() - ) - ] -) -``` - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig -from supertokens_python.recipe import thirdparty, passwordless, session - -^{form_contactMethod_import_Python} -init( - app_info=InputAppInfo( - app_name="^{form_appName}", - api_domain="^{form_apiDomain}", - website_domain="^{form_websiteDomain}", - api_base_path="^{form_apiBasePath}", - website_base_path="^{form_websiteBasePath}" - ), - supertokens_config=SupertokensConfig( - ^{coreInjector_connection_uri_comment_with_hash} - connection_uri=^{coreInjector_uri} - ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} - ), - framework='django', - recipe_list=[ - session.init(), # initializes session features - thirdparty.init( - # TODO: See next steps for third party provider setup - ), - passwordless.init( - flow_type="^{form_flowType}", - contact_config=^{form_contactMethod_initialize_Python}() - ) - ], - mode='asgi' # use wsgi if you are running django server in sync mode -) -``` - - - - - - - - - - - - -## 3) Initialise Social login providers - - - - -Populate the `providers` array with the third party auth providers you want. - - - - -```tsx -import SuperTokens from "supertokens-node"; -import ThirdParty from "supertokens-node/recipe/thirdparty" -import Passwordless from "supertokens-node/recipe/passwordless" - -SuperTokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "...", - }, - recipeList: [ - //highlight-start - Passwordless.init({ - flowType: "^{form_flowType}", - contactMethod: "^{form_contactMethod}", - }), - ThirdParty.init({ - // We have provided you with development keys which you can use for testing. - // IMPORTANT: Please replace them with your own OAuth keys for production use. - signInAndUpFeature: { - providers: [{ - config: { - thirdPartyId: "google", - clients: [{ - clientId: "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", - clientSecret: "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" - }] - } - }, { - config: { - thirdPartyId: "github", - clients: [{ - clientId: "467101b197249757c71f", - clientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd" - }] - } - }, { - config: { - thirdPartyId: "apple", - clients: [{ - clientId: "4398792-io.supertokens.example.service", - additionalConfig: { - keyId: "7M48Y4RYDL", - privateKey: - "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----", - teamId: "YWQCXGJRJL", - } - }] - } - }], - } - //highlight-end - }), - // ... - ] -}); -``` - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/thirdparty" - "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" -) - -func main() { - - // Inside supertokens.Init -> RecipeList - thirdparty.Init(&tpmodels.TypeInput{ - // highlight-start - SignInAndUpFeature: tpmodels.TypeInputSignInAndUp{ - Providers: []tpmodels.ProviderInput{ - // We have provided you with development keys which you can use for testing. - // IMPORTANT: Please replace them with your own OAuth keys for production use. - { - Config: tpmodels.ProviderConfig{ - ThirdPartyId: "google", - Clients: []tpmodels.ProviderClientConfig{ - { - ClientID: "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", - ClientSecret: "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", - }, - }, - }, - }, - { - Config: tpmodels.ProviderConfig{ - ThirdPartyId: "github", - Clients: []tpmodels.ProviderClientConfig{ - { - ClientID: "467101b197249757c71f", - ClientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd", - }, - }, - }, - }, - { - Config: tpmodels.ProviderConfig{ - ThirdPartyId: "apple", - Clients: []tpmodels.ProviderClientConfig{ - { - ClientID: "4398792-io.supertokens.example.service", - AdditionalConfig: map[string]interface{}{ - "keyId": "7M48Y4RYDL", - "privateKey": "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----", - "teamId": "YWQCXGJRJL", - }, - }, - }, - }, - }, - }, - }, - // highlight-end - }) -} -``` - - - -```python -from supertokens_python.recipe.thirdparty.provider import ProviderInput, ProviderConfig, ProviderClientConfig -from supertokens_python.recipe import thirdparty - -# Inside init -thirdparty.init( - # highlight-start - sign_in_and_up_feature=thirdparty.SignInAndUpFeature( - providers=[ - # We have provided you with development keys which you can use for testing. - # IMPORTANT: Please replace them with your own OAuth keys for production use. - ProviderInput( - config=ProviderConfig( - third_party_id="google", - clients=[ - ProviderClientConfig( - client_id="1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", - client_secret="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", - ), - ], - ), - ), - ProviderInput( - config=ProviderConfig( - third_party_id="github", - clients=[ - ProviderClientConfig( - client_id="467101b197249757c71f", - client_secret="e97051221f4b6426e8fe8d51486396703012f5bd", - ) - ], - ), - ), - ProviderInput( - config=ProviderConfig( - third_party_id="apple", - clients=[ - ProviderClientConfig( - client_id="io.supertokens.example.service", - additional_config={ - "keyId": "7M48Y4RYDL", - "privateKey": "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----", - "teamId": "YWQCXGJRJL" - }, - ), - ], - ), - ), - ] - ) - # highlight-end -) -``` - - - - -**When you want to generate your own keys**, please refer to the corresponding documentation to get your client ids and client secrets for each of the below providers: - -
      -Google - -- Generate your client ID and secret by following the [docs here](https://support.google.com/cloud/answer/6158849?hl=en) -- Set the authorisation callback URL to `^{form_websiteDomain}^{form_websiteBasePathForCallbacks}/callback/google` - -
      - -
      -Github - -- Generate your client ID and secret by following the [docs here](https://docs.github.com/en/developers/apps/creating-an-oauth-app) -- Set the authorisation callback URL to `^{form_websiteDomain}^{form_websiteBasePathForCallbacks}/callback/github` - -
      - -
      -Apple - -- Generate your client ID and secret by following [this article](https://medium.com/identity-beyond-borders/how-to-configure-sign-in-with-apple-77c61e336003) -- Set the authorisation callback URL to `^{form_apiDomain}^{form_apiBasePathForCallbacks}/callback/apple`. Note that Apple doesn't allow `localhost` in the URL. So if you are in dev mode, you can use the dev keys we have provided above. - -
      - -
      - -:::important -You can find the list of built in providers [here](../../common-customizations/signup-form/built-in-providers). To add a provider that is not listed, you can follow our guide on [setting up custom providers](../../common-customizations/signup-form/custom-providers). -::: - -
      - -## 4) Add the SuperTokens APIs & CORS setup - - - - - - - - -:::important -- Add the `middleware` BEFORE all your routes. -- Add the `cors` middleware BEFORE the SuperTokens middleware as shown below. -::: - -```tsx -import express from "express"; -import cors from "cors"; -import supertokens from "supertokens-node"; -import {middleware} from "supertokens-node/framework/express"; - -let app = express(); - -app.use(cors({ - // highlight-start - origin: "^{form_websiteDomain}", - allowedHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], - credentials: true, - // highlight-end -})); - -// IMPORTANT: CORS should be before the below line. -// highlight-next-line -app.use(middleware()); - -// ...your API routes -``` -This `middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): -- `POST ^{form_apiBasePath}/signinup/code`: For starting the passwordless login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/resend`: To generate and resend a code during an already started login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/consume`: For finishing the passwordless login/sign up process -- `GET ^{form_apiBasePath}/passwordless/email/exists`: To check if an email is already signed up -- `GET ^{form_apiBasePath}/passwordless/phonenumber/exists`: To check if a phonenumber is already signed up -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. - - - - -Register the `plugin`. - -```tsx -import Hapi from "@hapi/hapi"; -import supertokens from "supertokens-node"; -import { plugin } from "supertokens-node/framework/hapi"; - -const server = Hapi.server({ - port: 8000, - routes: { - // highlight-start - cors: { - origin: ["^{form_websiteDomain}"], - additionalHeaders: [...supertokens.getAllCORSHeaders()], - credentials: true, - } - // highlight-end - } -}); - -(async() => { - // highlight-next-line - await server.register(plugin); - - await server.start(); -})(); - -// ...your API routes -``` -This `plugin` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)) as well take care of all the errors thrown by the Supertokens library: -- `POST ^{form_apiBasePath}/signinup/code`: For starting the passwordless login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/resend`: To generate and resend a code during an already started login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/consume`: For finishing the passwordless login/sign up process -- `GET ^{form_apiBasePath}/passwordless/email/exists`: To check if an email is already signed up -- `GET ^{form_apiBasePath}/passwordless/phonenumber/exists`: To check if a phonenumber is already signed up -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. - - - - - -Register the `plugin`. Also register [`@fastify/formbody`](https://github.com/fastify/fastify-formbody) plugin. - -```tsx -import cors from "@fastify/cors"; -import supertokens from "supertokens-node"; -import { plugin } from "supertokens-node/framework/fastify"; -import formDataPlugin from "@fastify/formbody"; - -import fastifyImport from "fastify"; -let fastify = fastifyImport(); - -// ...other middlewares -(async () => { - // highlight-start - await fastify.register(cors, { - origin: "^{form_websiteDomain}", - allowedHeaders: ['Content-Type', ...supertokens.getAllCORSHeaders()], - credentials: true, - }) - // highlight-end - // highlight-next-line - await fastify.register(formDataPlugin); - // highlight-next-line - await fastify.register(plugin); - - await fastify.listen(3000); -})(); - -// ...your API routes -``` -This `plugin` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): -- `POST ^{form_apiBasePath}/signinup/code`: For starting the passwordless login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/resend`: To generate and resend a code during an already started login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/consume`: For finishing the passwordless login/sign up process -- `GET ^{form_apiBasePath}/passwordless/email/exists`: To check if an email is already signed up -- `GET ^{form_apiBasePath}/passwordless/phonenumber/exists`: To check if a phonenumber is already signed up -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. - - - - - -:::important -Add the `middleware` BEFORE all your routes. -::: - -```tsx -import Koa from "koa"; -import cors from '@koa/cors'; -import supertokens from "supertokens-node"; -import { middleware } from "supertokens-node/framework/koa"; - -let app = new Koa(); - -// ...other middlewares -app.use(cors({ - // highlight-start - origin: "^{form_websiteDomain}", - allowHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], - credentials: true, - // highlight-end -})); - -// highlight-next-line -app.use(middleware()); - -// ...your API routes -``` -This `middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): -- `POST ^{form_apiBasePath}/signinup/code`: For starting the passwordless login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/resend`: To generate and resend a code during an already started login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/consume`: For finishing the passwordless login/sign up process -- `GET ^{form_apiBasePath}/passwordless/email/exists`: To check if an email is already signed up -- `GET ^{form_apiBasePath}/passwordless/phonenumber/exists`: To check if a phonenumber is already signed up -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. - - - - - -:::important -Add the `middleware` BEFORE all your routes. -::: - -```tsx -import {RestApplication} from '@loopback/rest'; -import supertokens from "supertokens-node"; -import {middleware} from "supertokens-node/framework/loopback"; - -let app = new RestApplication({ - rest: { - cors: { - // highlight-start - origin: "^{form_websiteDomain}", - allowedHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], - credentials: true - // highlight-end - } - } -}); - -// highlight-next-line -app.middleware(middleware); - -// ...your API routes -``` -This `middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): -- `POST ^{form_apiBasePath}/signinup/code`: For starting the passwordless login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/resend`: To generate and resend a code during an already started login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/consume`: For finishing the passwordless login/sign up process -- `GET ^{form_apiBasePath}/passwordless/email/exists`: To check if an email is already signed up -- `GET ^{form_apiBasePath}/passwordless/phonenumber/exists`: To check if a phonenumber is already signed up -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. - - - - - - - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "net/http" - "strings" - - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - //highlight-start - http.ListenAndServe("SERVER ADDRESS", corsMiddleware( - supertokens.Middleware(http.HandlerFunc(func(rw http.ResponseWriter, - //highlight-end - r *http.Request) { - // TODO: Handle your APIs.. - - })))) -} - -func corsMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(response http.ResponseWriter, r *http.Request) { - response.Header().Set("Access-Control-Allow-Origin", "^{form_websiteDomain}") - response.Header().Set("Access-Control-Allow-Credentials", "true") - if r.Method == "OPTIONS" { - // we add content-type + other headers used by SuperTokens - response.Header().Set("Access-Control-Allow-Headers", - strings.Join(append([]string{"Content-Type"}, - //highlight-start - supertokens.GetAllCORSHeaders()...), ",")) - //highlight-end - response.Header().Set("Access-Control-Allow-Methods", "*") - response.Write([]byte("")) - } else { - next.ServeHTTP(response, r) - } - }) -} -``` - - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "net/http" - - "github.com/gin-contrib/cors" - "github.com/gin-gonic/gin" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - router := gin.New() - - // CORS - router.Use(cors.New(cors.Config{ - AllowOrigins: []string{"^{form_websiteDomain}"}, - AllowMethods: []string{"GET", "POST", "DELETE", "PUT", "OPTIONS"}, - AllowHeaders: append([]string{"content-type"}, - // highlight-next-line - supertokens.GetAllCORSHeaders()...), - AllowCredentials: true, - })) - - // Adding the SuperTokens middleware - // highlight-start - router.Use(func(c *gin.Context) { - supertokens.Middleware(http.HandlerFunc( - func(rw http.ResponseWriter, r *http.Request) { - c.Next() - })).ServeHTTP(c.Writer, c.Request) - // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly - c.Abort() - }) - // highlight-end - - // Add APIs and start server -} -``` - - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "github.com/go-chi/chi" - "github.com/go-chi/cors" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - r := chi.NewRouter() - - // CORS - r.Use(cors.Handler(cors.Options{ - AllowedOrigins: []string{"^{form_websiteDomain}"}, - AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, - AllowedHeaders: append([]string{"Content-Type"}, - //highlight-next-line - supertokens.GetAllCORSHeaders()...), - AllowCredentials: true, - })) - - // SuperTokens Middleware - //highlight-next-line - r.Use(supertokens.Middleware) - - // Add APIs and start server -} -``` - - - -Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. - -```go -import ( - "net/http" - - "github.com/gorilla/handlers" - "github.com/gorilla/mux" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - // SuperTokens init... - - // Add APIs - - router := mux.NewRouter() - - // Adding handlers.CORS(options)(supertokens.Middleware(router))) - //highlight-start - http.ListenAndServe("SERVER ADDRESS", handlers.CORS( - handlers.AllowedHeaders(append([]string{"Content-Type"}, - supertokens.GetAllCORSHeaders()...)), - handlers.AllowedMethods([]string{"GET", "POST", "PUT", "HEAD", "OPTIONS"}), - handlers.AllowedOrigins([]string{"^{form_websiteDomain}"}), - handlers.AllowCredentials(), - )(supertokens.Middleware(router))) - //highlight-end -} -``` - - - -This `Middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): -- `POST ^{form_apiBasePath}/signinup/code`: For starting the passwordless login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/resend`: To generate and resend a code during an already started login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/consume`: For finishing the passwordless login/sign up process -- `GET ^{form_apiBasePath}/passwordless/email/exists`: To check if an email is already signed up -- `GET ^{form_apiBasePath}/passwordless/phonenumber/exists`: To check if a phonenumber is already signed up -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. - - - - - - - - -Use the `Middleware` (**BEFORE all your routes**) and the `get_all_cors_headers()` functions as shown below. - -```python -from supertokens_python import get_all_cors_headers -from fastapi import FastAPI -from starlette.middleware.cors import CORSMiddleware -from supertokens_python.framework.fastapi import get_middleware - -app = FastAPI() -# highlight-next-line -app.add_middleware(get_middleware()) - -# TODO: Add APIs - -app.add_middleware( - CORSMiddleware, - allow_origins=[ - "^{form_websiteDomain}" - ], - allow_credentials=True, - allow_methods=["GET", "PUT", "POST", "DELETE", "OPTIONS", "PATCH"], - # highlight-next-line - allow_headers=["Content-Type"] + get_all_cors_headers(), -) - -# TODO: start server -``` - - - - -- Use the `Middleware` (**BEFORE all your routes and after calling init function**) and the `get_all_cors_headers()` functions as shown below. -- Add a route to catch all paths and return a 404. This is needed because if we don't add this, then OPTIONS request for the APIs exposed by the `Middleware` will return a `404`. - -```python -from supertokens_python import get_all_cors_headers -from flask import Flask, abort -from flask_cors import CORS # type: ignore -from supertokens_python.framework.flask import Middleware - -app = Flask(__name__) -# highlight-next-line -Middleware(app) - -# TODO: Add APIs - -CORS( - app=app, - origins=[ - "^{form_websiteDomain}" - ], - supports_credentials=True, - # highlight-next-line - allow_headers=["Content-Type"] + get_all_cors_headers(), -) - -# This is required since if this is not there, then OPTIONS requests for -# the APIs exposed by the supertokens' Middleware will return a 404 -# highlight-start -@app.route('/', defaults={'u_path': ''}) # type: ignore -@app.route('/') # type: ignore -def catch_all(u_path: str): - abort(404) -# highlight-end - -# TODO: start server -``` - - - - -Use the `Middleware` and the `get_all_cors_headers()` functions as shown below in your `settings.py`. - -```python -from supertokens_python import get_all_cors_headers -from typing import List -from corsheaders.defaults import default_headers - -CORS_ORIGIN_WHITELIST = [ - "^{form_websiteDomain}" -] - -CORS_ALLOW_CREDENTIALS = True - -CORS_ALLOWED_ORIGINS = [ - "^{form_websiteDomain}" -] - -CORS_ALLOW_HEADERS: List[str] = list(default_headers) + [ - "Content-Type" - # highlight-next-line -] + get_all_cors_headers() - -INSTALLED_APPS = [ - 'corsheaders', - 'supertokens_python' -] - -MIDDLEWARE = [ - 'corsheaders.middleware.CorsMiddleware', - ..., - # highlight-next-line - 'supertokens_python.framework.django.django_middleware.middleware', -] -``` - - - - -This `Middleware` adds a few APIs (see all the APIs [here](https://app.swaggerhub.com/apis/supertokens/FDI)): -- `POST ^{form_apiBasePath}/signinup/code`: For starting the passwordless login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/resend`: To generate and resend a code during an already started login/sign up process -- `POST ^{form_apiBasePath}/signinup/code/consume`: For finishing the passwordless login/sign up process -- `GET ^{form_apiBasePath}/passwordless/email/exists`: To check if an email is already signed up -- `GET ^{form_apiBasePath}/passwordless/phonenumber/exists`: To check if a phonenumber is already signed up -- `POST ^{form_apiBasePath}/signinup`: For signing up/signing in a user using a thirdparty provider. - - - - - - - -## 5) Add the SuperTokens error handler -Add the `errorHandler` **AFTER all your routes**, but **BEFORE your error handler** - - - - - - -```tsx -import express from "express"; -import {errorHandler} from "supertokens-node/framework/express"; - -const app = express(); -// ...your API routes - -// highlight-start -// Add this AFTER all your routes -app.use(errorHandler()) -// highlight-end - -// your own error handler -app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => { - // TODO -}); - -``` - - - -No additional `errorHandler` is required. - - - - -Add the `errorHandler` **Before all your routes and plugin registration** - -```tsx -import Fastify from "fastify"; -import {errorHandler} from "supertokens-node/framework/fastify"; - -const fastify = Fastify(); - -// highlight-next-line -fastify.setErrorHandler(errorHandler()); - -// ...your API routes - -``` - - - -No additional `errorHandler` is required. - - - - -No additional `errorHandler` is required. - - - - - - -:::info -You can skip this step -::: - - - - -:::info -You can skip this step -::: - - - - -## 6) Setup the SuperTokens core - -You need to now setup an instance of the SuperTokens core for your app (that your backend should connect to). You have two options: -- [Managed service](./core/saas-setup) -- Self hosted with your own database ([With Docker](./core/with-docker) or [Without Docker](./core/without-docker)) \ No newline at end of file diff --git a/v2/thirdpartypasswordless/pre-built-ui/setup/frontend.mdx b/v2/thirdpartypasswordless/pre-built-ui/setup/frontend.mdx index 92433149b..bac6ce88b 100644 --- a/v2/thirdpartypasswordless/pre-built-ui/setup/frontend.mdx +++ b/v2/thirdpartypasswordless/pre-built-ui/setup/frontend.mdx @@ -4,718 +4,8 @@ title: "Step 1: Frontend" hide_title: true --- -import { PasswordlessFrontendForm } from "/src/components/snippetConfigForm/passwordlessFrontendForm"; +import Redirector from '/src/components/Redirector'; - - - -import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" -import TabItem from '@theme/TabItem'; -import {Question, Answer}from "/src/components/question" -import AppInfoForm from "/src/components/appInfoForm" -import RRDVersionSubTabs from "/src/components/tabs/RRDVersionSubTabs" -import NpmVersionOrYarnSubTabs from "/src/components/tabs/NpmVersionOrYarnSubTabs" -import PreBuiltUIInjector from "/src/components/prebuiltuiInjector" + -# Frontend Integration - -## Supported frameworks - -import TechStackSupport from "../../../community/reusableMD/supported-tech-stacks-frontend.mdx" - - - -# Automatic setup using CLI - -Run the following command in your terminal. -```bash -npx create-supertokens-app@latest --recipe=^{docsLinkRecipeName} -``` - -Once this is done, you can skip Step (1) and (2) in this section (see left nav bar) and move directly to setting up the SuperTokens core (Step 3). - -Or, you can manually integrate SuperTokens by following the steps below. - -# Manual setup steps below - -## 1) Install - - - - - - - - -```bash -npm i -s supertokens-auth-react -``` - - - - -```bash -npm i -s supertokens-auth-react supertokens-web-js -``` - - - - -```bash -yarn add supertokens-auth-react supertokens-web-js -``` - - - - - - - - - -Start by installing the SuperTokens Web SDK: - -```bash -npm i -s supertokens-web-js -``` - - - - - -Start by installing the SuperTokens Web SDK: - -```bash -npm i -s supertokens-web-js -``` - - - - - -Start by installing the SuperTokens Web SDK: - -```bash -yarn add supertokens-web-js -``` - - - - - - - - - - - -Start by installing the SuperTokens web SDK: - -```bash -npm i -s supertokens-web-js -``` - - - - - -Start by installing the SuperTokens web SDK: - -```bash -npm i -s supertokens-web-js -``` - - - - - -Start by installing the SuperTokens web SDK: - -```bash -yarn add supertokens-web-js -``` - - - - - - - - -:::important -SuperTokens does not support pre-built UI for mobile frameworks. Please refer to the [setup guide for custom UI](../../custom-ui/init/frontend) to integrate SuperTokens in your app. -::: - - - - - - - -## 2) Call the `init` function - - - - - - - -```tsx -import React from 'react'; - -// highlight-start -import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; -import Passwordless from "supertokens-auth-react/recipe/passwordless"; -import ThirdParty from "supertokens-auth-react/recipe/thirdparty"; -import Session from "supertokens-auth-react/recipe/session"; - -SuperTokens.init({ - appInfo: { - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - websiteDomain: "^{form_websiteDomain}", - apiBasePath: "^{form_apiBasePath}", - websiteBasePath: "^{form_websiteBasePath}" - }, - recipeList: [ - Passwordless.init({ - contactMethod: "^{form_contactMethod}", - }), - ThirdParty.init({ - signInAndUpFeature: { - providers: [ - ThirdParty.Github.init(), - ThirdParty.Google.init(), - ThirdParty.Facebook.init(), - ThirdParty.Apple.init(), - ], - } - }), - Session.init() - ] -}); -// highlight-end - - -/* Your App */ -class App extends React.Component { - render() { - return ( - // highlight-next-line - - {/*Your app components*/} - // highlight-next-line - - ); - } -} -``` - - - - - - - - - - -```tsx -import SuperTokens from 'supertokens-react-native'; - -SuperTokens.init({ - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}" -}); -``` - - - - - - - - - - - - - -Before we initialize the `supertokens-web-js` SDK let's see how we will use it in our Angular app - -**Architecture** - -- The `supertokens-web-js` SDK is responsible for session management and providing helper functions to check if a session exists, or validate the access token claims on the frontend (for example, to check for user roles before showing some UI). We will initialise this SDK on the root of your Angular app, so that all pages in your app can use it. -- We will create a `^{form_websiteBasePath}*` route in the Angular app which will render our pre built UI which will also need to be initialised, but only on that route. - -**Creating the `^{form_websiteBasePath}` route** - -- Use the Angular CLI to generate a new route - - ```bash - ng generate module auth --route auth --module app.module - ``` - -- Add the following code to your `auth` angular component - - ```tsx title="/app/auth/auth.component.ts" - import {init as supertokensUIInit} from "supertokens-auth-react-script"; - import supertokensUIPasswordless from "supertokens-auth-react-script/recipe/passwordless"; - import supertokensUIThirdParty from "supertokens-auth-react-script/recipe/thirdparty"; - import supertokensUISession from "supertokens-auth-react-script/recipe/session"; - 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: [ - supertokensUIPasswordless.init({ - contactMethod: "^{form_contactMethod}" - }), - supertokensUIThirdParty.init({ - signInAndUpFeature: { - providers: [ - supertokensUIThirdParty.Github.init(), - supertokensUIThirdParty.Google.init(), - supertokensUIThirdParty.Facebook.init(), - supertokensUIThirdParty.Apple.init(), - ] - } - }), - supertokensUISession.init(), - ], - }); - } - this.renderer.appendChild(this.document.body, script); - } - } - ``` - - In the `loadScript` function, we provide the SuperTokens config for the UI. We add the passwordless, session and social login recipes along with Github, Google, Facebook and Apple login buttons. - -- Initialize the `supertokens-web-js` SDK in your angular app's root component. This will provide session management across your entire application. - - ```tsx title="/app/app.component.ts " - import SuperTokens from "supertokens-web-js"; - import Session from "supertokens-web-js/recipe/session"; - - SuperTokens.init({ - appInfo: { - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}", - }, - recipeList: [ - Session.init(), - ], - }); - ``` - -
      - -
      - -
      - -
      - - - - - - - - - -Before we initialize the `supertokens-web-js` SDK let's see how we will use it in our Vue app - -**Architecture** - -- The `supertokens-web-js` SDK is responsible for session management and providing helper functions to check if a session exists, or validate the access token claims on the frontend (for example, to check for user roles before showing some UI). We will initialise this SDK on the root of your Vue app, so that all pages in your app can use it. -- We will create a `^{form_websiteBasePath}*` route in the Vue app which will render our pre built UI which will also need to be initialised, but only on that route. - -**Creating the `^{form_websiteBasePath}` route** - -- Create a new file `AuthView.vue`, this Vue component will be used to render the auth component: - ```tsx - import {init as supertokensUIInit} from "supertokens-auth-react-script"; - import supertokensUIPasswordless from "supertokens-auth-react-script/recipe/passwordless"; - import supertokensUIThirdParty from "supertokens-auth-react-script/recipe/thirdparty"; - import supertokensUISession from "supertokens-auth-react-script/recipe/session"; - - - - ``` - - In the `loadScript` function, we provide the SuperTokens config for the UI. We add the passwordless, session and social login recipes along with Github, Google, Facebook and Apple login buttons. - -- Initialize the `supertokens-web-js` SDK in your Vue app's `main.ts` file. This will provide session management across your entire application. - - ```tsx title="/main.ts " - // @ts-ignore - import { createApp } from "vue"; - import SuperTokens from "supertokens-web-js"; - import Session from "supertokens-web-js/recipe/session"; - // @ts-ignore - import App from "./App.vue"; - // @ts-ignore - import router from "./router"; - - SuperTokens.init({ - appInfo: { - appName: "^{form_appName}", - apiDomain: "^{form_apiDomain}", - apiBasePath: "^{form_apiBasePath}", - }, - recipeList: [ - Session.init(), - ], - }); - - const app = createApp(App); - - app.use(router); - - app.mount("#app"); - - ``` - - - - - - - - - -:::important -SuperTokens does not support pre-built UI for mobile frameworks. Please refer to the [setup guide for custom UI](../../custom-ui/init/frontend) to integrate SuperTokens in your app. -::: - - - -
      - - - - - -## 3) Setup Routing to show the login UI - - - - - - - - - - - -Call the `getSuperTokensRoutesForReactRouterDom` method from within any `react-router-dom` `Routes` component. - -```tsx -import React from 'react'; -import { - BrowserRouter, - Routes, - Route, - Link -} from "react-router-dom"; - -// highlight-next-line -import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; -import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui"; -^{prebuiltuiimport} -import * as reactRouterDom from "react-router-dom"; - -class App extends React.Component { - render() { - return ( - - - - {/*This renders the login UI on the ^{form_websiteBasePath} route*/} - // highlight-next-line - {getSuperTokensRoutesForReactRouterDom(reactRouterDom, [^{recipePreBuiltUINameCapitalLetters}])} - {/*Your app routes*/} - - - - ); - } -} -``` - - - - - -Call the `getSuperTokensRoutesForReactRouterDom` method from within any `react-router-dom` `Switch` component. - -```tsx -import React from 'react'; -import { - BrowserRouter, - Switch, - Route, - Link -} from "react-router-dom5"; - -// highlight-next-line -import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; -import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui"; -^{prebuiltuiimport} -import * as reactRouterDom from "react-router-dom"; - - -class App extends React.Component { - render() { - return ( - - - - {/*This renders the login UI on the ^{form_websiteBasePath} route*/} - // highlight-next-line - {getSuperTokensRoutesForReactRouterDom(reactRouterDom, [^{recipePreBuiltUINameCapitalLetters}])} - {/*Your app routes*/} - - - - ); - } -} -``` - - - - - - - - -Add the highlighted code snippet to your root level `render` function. - -```tsx -import React from 'react'; -^{prebuiltuiimport} -import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; -import { canHandleRoute, getRoutingComponent } from "supertokens-auth-react/ui"; - -class App extends React.Component { - render() { - // highlight-start - if (canHandleRoute([^{recipePreBuiltUINameCapitalLetters}])) { - // This renders the login UI on the ^{form_websiteBasePath} route - return getRoutingComponent([^{recipePreBuiltUINameCapitalLetters}]) - } - // highlight-end - - return ( - {/*Your app*/} - ); - } - -} -``` - - - - - - - - - - -Update your angular router so that all auth related requests load the `auth` component - -```tsx title="/app/app-routing.module.ts" -import { NgModule } from "@angular/core"; -import { RouterModule, Routes } from "@angular/router"; - -const routes: Routes = [ - // highlight-start - { - path: "^{form_websiteBasePath_withoutForwardSlash}", - // @ts-ignore - loadChildren: () => import("./auth/auth.module").then((m) => m.AuthModule), - }, - - // @ts-ignore - { path: "**", loadChildren: () => import("./home/home.module").then((m) => m.HomeModule) }, - // highlight-end -]; - -@NgModule({ - imports: [RouterModule.forRoot(routes)], - exports: [RouterModule], -}) -export class AppRoutingModule {} -``` - - - - - - - - -Update your Vue router so that all auth related requests load the `AuthView` component - -```tsx title="/router/index.ts" -// @ts-ignore -import { createRouter, createWebHistory } from "vue-router"; -// @ts-ignore -import HomeView from "../views/HomeView.vue"; -// @ts-ignore -import AuthView from "../views/AuthView.vue"; - -const router = createRouter({ - // @ts-ignore - history: createWebHistory(import.meta.env.BASE_URL), - routes: [ - { - path: "/", - name: "home", - component: HomeView, - }, - { - path: "^{form_websiteBasePath}/:pathMatch(.*)*", - name: "auth", - component: AuthView, - }, - ], -}); - -export default router; -``` - - - - - - - -:::important -SuperTokens does not support pre-built UI for mobile frameworks. Please refer to the [setup guide for custom UI](../../custom-ui/init/frontend) to integrate SuperTokens in your app. -::: - - - - - - - -## 4) View the login UI - - -^{form_addVisitWebsiteBasePathText} - - - -At this stage, you've successfully integrated your website with SuperTokens. The next section will guide you through setting up your backend. - diff --git a/v2/thirdpartypasswordless/quickstart/backend-setup.mdx b/v2/thirdpartypasswordless/quickstart/backend-setup.mdx new file mode 100644 index 000000000..c90b3cfd7 --- /dev/null +++ b/v2/thirdpartypasswordless/quickstart/backend-setup.mdx @@ -0,0 +1,1567 @@ +--- +id: backend-setup +title: Backend Setup +hide_title: true +show_ui_switcher: false +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +import PreBuiltUIInjector from "/src/components/prebuiltuiInjector" +import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; +import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" +import {Question, Answer}from "/src/components/question" +import RRDVersionSubTabs from "/src/components/tabs/RRDVersionSubTabs" +import AppInfoForm from "/src/components/appInfoForm" +import NodeJSFrameworkSubTabsServerless from "/src/components/tabs/NodeJSFrameworkSubTabsServerless" +import CoreInjector from "/src/components/coreInjector" +import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs" +import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" +import BackendSDKCasing from "/src/components/BackendSDKCasing" + + +import PasswordlessQuickstartBackendCustomMagicLink from '../../community/reusableMD/passwordless-quickstart-backend-custom-magic-link.mdx' + +import { ConditionalSection } from "/src/components/snippetConfigForm"; +import { PasswordlessBackendForm } from "/src/components/snippetConfigForm/passwordlessBackendForm"; + +# Backend Setup + +Let's got through the changes required so that your backend can expose the **SuperTokens** authentication features. + +## 1. Install the SDK + + + + + +Run the following command in your terminal to install the package. + + + + +```bash +npm i -s supertokens-node +``` + + + + +```bash +go get github.com/supertokens/supertokens-golang +``` + + + + +```bash +pip install supertokens-python +``` + + + + + + +## 2. Initialize the SDK + +You will have to intialize the **Backend SDK** alongside the code that starts your server. +The init call will include [configuration details](../appinfo) for your app, how the backend will connect to the **SuperTokens Core**, as well as the **Recipes** that will be used in your setup. + + + + + + +Add the code below to your server's init file. + + + + + + + +```tsx +import supertokens from "supertokens-node"; +import Session from "supertokens-node/recipe/session"; +import ThirdParty from "supertokens-node/recipe/thirdparty" +import Passwordless from "supertokens-node/recipe/passwordless" + +supertokens.init({ + framework: "express", + supertokens: { + ^{coreInjector_connection_uri_comment} + connectionURI: ^{coreInjector_uri} + ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, + }, + appInfo: { + // learn more about this on https://supertokens.com/docs/session/appinfo + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + websiteDomain: "^{form_websiteDomain}", + apiBasePath: "^{form_apiBasePath}", + websiteBasePath: "^{form_websiteBasePath}" + }, + recipeList: [ + Passwordless.init({ + flowType: "^{form_flowType}", + contactMethod: "^{form_contactMethod}" + }), + ThirdParty.init(/*TODO: See next steps for third party provider setup */), + Session.init() // initializes session features + ] +}); +``` + + + + + +```tsx +import supertokens from "supertokens-node"; +import Session from "supertokens-node/recipe/session"; +import ThirdParty from "supertokens-node/recipe/thirdparty" +import Passwordless from "supertokens-node/recipe/passwordless" + +supertokens.init({ + framework: "hapi", + supertokens: { + ^{coreInjector_connection_uri_comment} + connectionURI: ^{coreInjector_uri} + ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, + }, + appInfo: { + // learn more about this on https://supertokens.com/docs/session/appinfo + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + websiteDomain: "^{form_websiteDomain}", + apiBasePath: "^{form_apiBasePath}", + websiteBasePath: "^{form_websiteBasePath}" + }, + recipeList: [ + Passwordless.init({ + flowType: "^{form_flowType}", + contactMethod: "^{form_contactMethod}" + }), + ThirdParty.init(/*TODO: See next steps for third party provider setup */), + Session.init() // initializes session features + ] +}); +``` + + + + +```tsx +import supertokens from "supertokens-node"; +import Session from "supertokens-node/recipe/session"; +import ThirdParty from "supertokens-node/recipe/thirdparty" +import Passwordless from "supertokens-node/recipe/passwordless" + +supertokens.init({ + framework: "fastify", + supertokens: { + ^{coreInjector_connection_uri_comment} + connectionURI: ^{coreInjector_uri} + ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, + }, + appInfo: { + // learn more about this on https://supertokens.com/docs/session/appinfo + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + websiteDomain: "^{form_websiteDomain}", + apiBasePath: "^{form_apiBasePath}", + websiteBasePath: "^{form_websiteBasePath}" + }, + recipeList: [ + Passwordless.init({ + flowType: "^{form_flowType}", + contactMethod: "^{form_contactMethod}" + }), + ThirdParty.init(/*TODO: See next steps for third party provider setup */), + Session.init() // initializes session features + ] +}); +``` + + + + +```tsx +import supertokens from "supertokens-node"; +import Session from "supertokens-node/recipe/session"; +import ThirdParty from "supertokens-node/recipe/thirdparty" +import Passwordless from "supertokens-node/recipe/passwordless" + +supertokens.init({ + framework: "koa", + supertokens: { + ^{coreInjector_connection_uri_comment} + connectionURI: ^{coreInjector_uri} + ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, + }, + appInfo: { + // learn more about this on https://supertokens.com/docs/session/appinfo + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + websiteDomain: "^{form_websiteDomain}", + apiBasePath: "^{form_apiBasePath}", + websiteBasePath: "^{form_websiteBasePath}" + }, + recipeList: [ + Passwordless.init({ + flowType: "^{form_flowType}", + contactMethod: "^{form_contactMethod}" + }), + ThirdParty.init(/*TODO: See next steps for third party provider setup */), + Session.init() // initializes session features + ] +}); +``` + + + + +```tsx +import supertokens from "supertokens-node"; +import Session from "supertokens-node/recipe/session"; +import ThirdParty from "supertokens-node/recipe/thirdparty" +import Passwordless from "supertokens-node/recipe/passwordless" + +supertokens.init({ + framework: "loopback", + supertokens: { + ^{coreInjector_connection_uri_comment} + connectionURI: ^{coreInjector_uri} + ^{coreInjector_api_key_commented}apiKey: ^{coreInjector_api_key}, + }, + appInfo: { + // learn more about this on https://supertokens.com/docs/session/appinfo + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + websiteDomain: "^{form_websiteDomain}", + apiBasePath: "^{form_apiBasePath}", + websiteBasePath: "^{form_websiteBasePath}" + }, + recipeList: [ + Passwordless.init({ + flowType: "^{form_flowType}", + contactMethod: "^{form_contactMethod}" + }), + ThirdParty.init(/*TODO: See next steps for third party provider setup */), + Session.init() // initializes session features + ] +}); +``` + + + + + +```go +import ( + "github.com/supertokens/supertokens-golang/recipe/passwordless" + "github.com/supertokens/supertokens-golang/recipe/passwordless/plessmodels" + "github.com/supertokens/supertokens-golang/recipe/session" + "github.com/supertokens/supertokens-golang/recipe/thirdparty" + "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" + "github.com/supertokens/supertokens-golang/supertokens" +) + +func main() { + apiBasePath := "^{form_apiBasePath}" + websiteBasePath := "^{form_websiteBasePath}" + err := supertokens.Init(supertokens.TypeInput{ + Supertokens: &supertokens.ConnectionInfo{ + ^{coreInjector_connection_uri_comment} + ConnectionURI: ^{coreInjector_uri} + ^{coreInjector_api_key_commented}APIKey: ^{coreInjector_api_key}, + }, + AppInfo: supertokens.AppInfo{ + AppName: "^{form_appName}", + APIDomain: "^{form_apiDomain}", + WebsiteDomain: "^{form_websiteDomain}", + APIBasePath: &apiBasePath, + WebsiteBasePath: &websiteBasePath, + }, + RecipeList: []supertokens.Recipe{ + passwordless.Init(plessmodels.TypeInput{ + FlowType: "^{form_flowType}", + ^{form_contactMethod_sendCB_Go} + }), + thirdparty.Init(&tpmodels.TypeInput{ + SignInAndUpFeature: tpmodels.TypeInputSignInAndUp{ + /*TODO: See next steps for third party provider setup */ + }, + }), + session.Init(nil), // initializes session features + }, + }) + + if err != nil { + panic(err.Error()) + } +} +``` + + + + + + +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import thirdparty, passwordless, session + +^{form_contactMethod_import_Python} +init( + app_info=InputAppInfo( + app_name="^{form_appName}", + api_domain="^{form_apiDomain}", + website_domain="^{form_websiteDomain}", + api_base_path="^{form_apiBasePath}", + website_base_path="^{form_websiteBasePath}" + ), + supertokens_config=SupertokensConfig( + ^{coreInjector_connection_uri_comment_with_hash} + connection_uri=^{coreInjector_uri} + ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} + ), + framework='fastapi', + recipe_list=[ + session.init(), # initializes session features + thirdparty.init( + # TODO: See next steps for third party provider setup + ), + passwordless.init( + flow_type="^{form_flowType}", + contact_config=^{form_contactMethod_initialize_Python}() + ) + ], + mode='asgi' # use wsgi if you are running using gunicorn +) +``` + + + + +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import thirdparty, passwordless, session + +^{form_contactMethod_import_Python} +init( + app_info=InputAppInfo( + app_name="^{form_appName}", + api_domain="^{form_apiDomain}", + website_domain="^{form_websiteDomain}", + api_base_path="^{form_apiBasePath}", + website_base_path="^{form_websiteBasePath}" + ), + supertokens_config=SupertokensConfig( + ^{coreInjector_connection_uri_comment_with_hash} + connection_uri=^{coreInjector_uri} + ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} + ), + framework='flask', + recipe_list=[ + session.init(), # initializes session features + thirdparty.init( + # TODO: See next steps for third party provider setup + ), + passwordless.init( + flow_type="^{form_flowType}", + contact_config=^{form_contactMethod_initialize_Python}() + ) + ] +) +``` + + + + +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import thirdparty, passwordless, session + +^{form_contactMethod_import_Python} +init( + app_info=InputAppInfo( + app_name="^{form_appName}", + api_domain="^{form_apiDomain}", + website_domain="^{form_websiteDomain}", + api_base_path="^{form_apiBasePath}", + website_base_path="^{form_websiteBasePath}" + ), + supertokens_config=SupertokensConfig( + ^{coreInjector_connection_uri_comment_with_hash} + connection_uri=^{coreInjector_uri} + ^{coreInjector_api_key_commented_with_hash}api_key=^{coreInjector_api_key} + ), + framework='django', + recipe_list=[ + session.init(), # initializes session features + thirdparty.init( + # TODO: See next steps for third party provider setup + ), + passwordless.init( + flow_type="^{form_flowType}", + contact_config=^{form_contactMethod_initialize_Python}() + ) + ], + mode='asgi' # use wsgi if you are running django server in sync mode +) +``` + + + + + + + + + + + + + + + + + + + + + + + + +## 3. Initialise Social Login Providers + + + +Populate the `providers` array with the third party auth providers you want. + + + + +```tsx +import SuperTokens from "supertokens-node"; +import ThirdParty from "supertokens-node/recipe/thirdparty" +import Passwordless from "supertokens-node/recipe/passwordless" + +SuperTokens.init({ + appInfo: { + apiDomain: "...", + appName: "...", + websiteDomain: "...", + }, + recipeList: [ + Passwordless.init({ + flowType: "^{form_flowType}", + contactMethod: "^{form_contactMethod}" + }), + ThirdParty.init({ + //highlight-start + // We have provided you with development keys which you can use for testing. + // IMPORTANT: Please replace them with your own OAuth keys for production use. + signInAndUpFeature: { + providers: [{ + config: { + thirdPartyId: "google", + clients: [{ + clientId: "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", + clientSecret: "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" + }] + } + }, { + config: { + thirdPartyId: "github", + clients: [{ + clientId: "467101b197249757c71f", + clientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd" + }] + } + }, { + config: { + thirdPartyId: "apple", + clients: [{ + clientId: "4398792-io.supertokens.example.service", + additionalConfig: { + keyId: "7M48Y4RYDL", + privateKey: + "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----", + teamId: "YWQCXGJRJL", + } + }] + } + }], + } + //highlight-end + }), + // ... + ] +}); +``` + + + +```go +import ( + "github.com/supertokens/supertokens-golang/recipe/thirdparty" + "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" +) + +func main() { + + // Inside supertokens.Init -> RecipeList + thirdparty.Init(&tpmodels.TypeInput{ + // highlight-start + SignInAndUpFeature: tpmodels.TypeInputSignInAndUp{ + Providers: []tpmodels.ProviderInput{ + // We have provided you with development keys which you can use for testing. + // IMPORTANT: Please replace them with your own OAuth keys for production use. + { + Config: tpmodels.ProviderConfig{ + ThirdPartyId: "google", + Clients: []tpmodels.ProviderClientConfig{ + { + ClientID: "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", + ClientSecret: "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", + }, + }, + }, + }, + { + Config: tpmodels.ProviderConfig{ + ThirdPartyId: "github", + Clients: []tpmodels.ProviderClientConfig{ + { + ClientID: "467101b197249757c71f", + ClientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd", + }, + }, + }, + }, + { + Config: tpmodels.ProviderConfig{ + ThirdPartyId: "apple", + Clients: []tpmodels.ProviderClientConfig{ + { + ClientID: "4398792-io.supertokens.example.service", + AdditionalConfig: map[string]interface{}{ + "keyId": "7M48Y4RYDL", + "privateKey": "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----", + "teamId": "YWQCXGJRJL", + }, + }, + }, + }, + }, + }, + }, + // highlight-end + }) +} +``` + + + +```python +from supertokens_python.recipe.thirdparty.provider import ProviderInput, ProviderConfig, ProviderClientConfig +from supertokens_python.recipe import thirdparty + +# Inside init +thirdparty.init( + # highlight-start + sign_in_and_up_feature=thirdparty.SignInAndUpFeature( + providers=[ + # We have provided you with development keys which you can use for testing. + # IMPORTANT: Please replace them with your own OAuth keys for production use. + ProviderInput( + config=ProviderConfig( + third_party_id="google", + clients=[ + ProviderClientConfig( + client_id="1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", + client_secret="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", + ), + ], + ), + ), + ProviderInput( + config=ProviderConfig( + third_party_id="github", + clients=[ + ProviderClientConfig( + client_id='467101b197249757c71f', + client_secret='e97051221f4b6426e8fe8d51486396703012f5bd' + ), + ], + ), + ), + ProviderInput( + config=ProviderConfig( + third_party_id="apple", + clients=[ + ProviderClientConfig( + client_id="4398792-io.supertokens.example.service", + additional_config={ + "keyId": "7M48Y4RYDL", + "privateKey": "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----", + "teamId": "YWQCXGJRJL" + }, + ), + ], + ), + ), + ] + ) + # highlight-end +) +``` + + + + +**When you want to generate your own keys**, please refer to the corresponding documentation to get your client ids and client secrets for each of the below providers: + +
      +Google + +- Generate your client ID and secret by following the [docs here](https://support.google.com/cloud/answer/6158849?hl=en) +- Set the authorisation callback URL to `^{form_websiteDomain}^{form_websiteBasePathForCallbacks}/callback/google` + +
      + +
      +Github + +- Generate your client ID and secret by following the [docs here](https://docs.github.com/en/developers/apps/creating-an-oauth-app) +- Set the authorisation callback URL to `^{form_websiteDomain}^{form_websiteBasePathForCallbacks}/callback/github` + +
      + +
      +Apple + +- Generate your client ID and secret by following [this article](https://medium.com/identity-beyond-borders/how-to-configure-sign-in-with-apple-77c61e336003) +- Set the authorisation callback URL to `^{form_apiDomain}^{form_apiBasePathForCallbacks}/callback/apple`. Note that Apple doesn't allow `localhost` in the URL. So if you are in dev mode, you can use the dev keys we have provided above. + +
      + +:::important +You can find the list of built in providers [here](../common-customizations/signup-form/built-in-providers). To add a provider that is not listed, you can follow our guide on [setting up custom providers](../common-customizations/signup-form/custom-providers). +::: + +
      + +## 4. Add the SuperTokens APIs and Configure CORS + + + + + +Now that the SDK is initialized you need to expose the endpoints that will be used by the frontend SDKs. +Besides this, your server's CORS, Cross-Origin Resource Sharing, settings should be updated to allow the use of the authentication headers required by **SuperTokens**. + + + + + + + + +:::important +- Add the `middleware` BEFORE all your routes. +- Add the `cors` middleware BEFORE the SuperTokens middleware as shown below. +::: + +```tsx +import express from "express"; +import cors from "cors"; +import supertokens from "supertokens-node"; +import { middleware } from "supertokens-node/framework/express"; + +let app = express(); + +app.use(cors({ + // highlight-start + origin: "^{form_websiteDomain}", + allowedHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], + credentials: true, + // highlight-end +})); + +// IMPORTANT: CORS should be before the below line. +// highlight-next-line +app.use(middleware()); + +// ...your API routes +``` + + + + +Register the `plugin`. + +```tsx +import Hapi from "@hapi/hapi"; +import supertokens from "supertokens-node"; +import { plugin } from "supertokens-node/framework/hapi"; + +let server = Hapi.server({ + port: 8000, + routes: { + // highlight-start + cors: { + origin: ["^{form_websiteDomain}"], + additionalHeaders: [...supertokens.getAllCORSHeaders()], + credentials: true, + } + // highlight-end + } +}); + +(async () => { + // highlight-next-line + await server.register(plugin); + + await server.start(); +})(); + +// ...your API routes +``` + + + + +Register the `plugin`. Also register [`@fastify/formbody`](https://github.com/fastify/fastify-formbody) plugin. + +```tsx +import cors from "@fastify/cors"; +import supertokens from "supertokens-node"; +import { plugin } from "supertokens-node/framework/fastify"; +import formDataPlugin from "@fastify/formbody"; + +import fastifyImport from "fastify"; + +let fastify = fastifyImport(); + +// ...other middlewares +// highlight-start +fastify.register(cors, { + origin: "^{form_websiteDomain}", + allowedHeaders: ['Content-Type', ...supertokens.getAllCORSHeaders()], + credentials: true, +}); +// highlight-end + +(async () => { + // highlight-next-line + await fastify.register(formDataPlugin); + // highlight-next-line + await fastify.register(plugin); + + await fastify.listen(8000); +})(); + +// ...your API routes +``` + + + + +:::important +Add the `middleware` BEFORE all your routes. +::: + +```tsx +import Koa from "koa"; +import cors from '@koa/cors'; +import supertokens from "supertokens-node"; +import { middleware } from "supertokens-node/framework/koa"; + +let app = new Koa(); + +app.use(cors({ + // highlight-start + origin: "^{form_websiteDomain}", + allowHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], + credentials: true, + // highlight-end +})); + +// highlight-next-line +app.use(middleware()); + +// ...your API routes +``` + + + + +:::important +Add the `middleware` BEFORE all your routes. +::: + +```tsx +import { RestApplication } from "@loopback/rest"; +import supertokens from "supertokens-node"; +import { middleware } from "supertokens-node/framework/loopback"; + +let app = new RestApplication({ + rest: { + cors: { + // highlight-start + origin: "^{form_websiteDomain}", + allowedHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], + credentials: true + // highlight-end + } + } +}); + +// highlight-next-line +app.middleware(middleware); + +// ...your API routes +``` + + + + + + + + +Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. + +```go +import ( + "net/http" + "strings" + + "github.com/supertokens/supertokens-golang/supertokens" +) + +func main() { + // SuperTokens init... + + //highlight-start + http.ListenAndServe("SERVER ADDRESS", corsMiddleware( + supertokens.Middleware(http.HandlerFunc(func(rw http.ResponseWriter, + //highlight-end + r *http.Request) { + // TODO: Handle your APIs.. + + })))) +} + +func corsMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(response http.ResponseWriter, r *http.Request) { + response.Header().Set("Access-Control-Allow-Origin", "^{form_websiteDomain}") + response.Header().Set("Access-Control-Allow-Credentials", "true") + if r.Method == "OPTIONS" { + // we add content-type + other headers used by SuperTokens + response.Header().Set("Access-Control-Allow-Headers", + strings.Join(append([]string{"Content-Type"}, + //highlight-start + supertokens.GetAllCORSHeaders()...), ",")) + //highlight-end + response.Header().Set("Access-Control-Allow-Methods", "*") + response.Write([]byte("")) + } else { + next.ServeHTTP(response, r) + } + }) +} +``` + + + + +Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. + +```go +import ( + "net/http" + + "github.com/gin-contrib/cors" + "github.com/gin-gonic/gin" + "github.com/supertokens/supertokens-golang/supertokens" +) + +func main() { + // SuperTokens init... + + router := gin.New() + + // CORS + router.Use(cors.New(cors.Config{ + AllowOrigins: []string{"^{form_websiteDomain}"}, + AllowMethods: []string{"GET", "POST", "DELETE", "PUT", "OPTIONS"}, + AllowHeaders: append([]string{"content-type"}, + // highlight-next-line + supertokens.GetAllCORSHeaders()...), + AllowCredentials: true, + })) + + // Adding the SuperTokens middleware + // highlight-start + router.Use(func(c *gin.Context) { + supertokens.Middleware(http.HandlerFunc( + func(rw http.ResponseWriter, r *http.Request) { + c.Next() + })).ServeHTTP(c.Writer, c.Request) + // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly + c.Abort() + }) + // highlight-end + + // Add APIs and start server +} +``` + + + + +Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. + +```go +import ( + "github.com/go-chi/chi" + "github.com/go-chi/cors" + "github.com/supertokens/supertokens-golang/supertokens" +) + +func main() { + // SuperTokens init... + + r := chi.NewRouter() + + // CORS + r.Use(cors.Handler(cors.Options{ + AllowedOrigins: []string{"^{form_websiteDomain}"}, + AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, + AllowedHeaders: append([]string{"Content-Type"}, + //highlight-next-line + supertokens.GetAllCORSHeaders()...), + AllowCredentials: true, + })) + + // SuperTokens Middleware + //highlight-next-line + r.Use(supertokens.Middleware) + + // Add APIs and start server +} +``` + + + +Use the `supertokens.Middleware` and the `supertokens.GetAllCORSHeaders()` functions as shown below. + +```go +import ( + "net/http" + + "github.com/gorilla/handlers" + "github.com/gorilla/mux" + "github.com/supertokens/supertokens-golang/supertokens" +) + +func main() { + // SuperTokens init... + + // TODO: Add APIs + + router := mux.NewRouter() + + // Adding handlers.CORS(options)(supertokens.Middleware(router))) + //highlight-start + http.ListenAndServe("SERVER ADDRESS", handlers.CORS( + handlers.AllowedHeaders(append([]string{"Content-Type"}, + supertokens.GetAllCORSHeaders()...)), + handlers.AllowedMethods([]string{"GET", "POST", "PUT", "HEAD", "OPTIONS"}), + handlers.AllowedOrigins([]string{"^{form_websiteDomain}"}), + handlers.AllowCredentials(), + )(supertokens.Middleware(router))) + //highlight-end +} +``` + + + + + + + + + +Use the `Middleware` (**BEFORE all your routes**) and the `get_all_cors_headers()` functions as shown below. + +```python +from supertokens_python import get_all_cors_headers +from fastapi import FastAPI +from starlette.middleware.cors import CORSMiddleware +from supertokens_python.framework.fastapi import get_middleware + +app = FastAPI() +# highlight-next-line +app.add_middleware(get_middleware()) + +# TODO: Add APIs + +app.add_middleware( + CORSMiddleware, + allow_origins=[ + "^{form_websiteDomain}" + ], + allow_credentials=True, + allow_methods=["GET", "PUT", "POST", "DELETE", "OPTIONS", "PATCH"], + # highlight-next-line + allow_headers=["Content-Type"] + get_all_cors_headers(), +) + +# TODO: start server +``` + + + + +- Use the `Middleware` (**BEFORE all your routes and after calling init function**) and the `get_all_cors_headers()` functions as shown below. +- Add a route to catch all paths and return a 404. This is needed because if we don't add this, then OPTIONS request for the APIs exposed by the `Middleware` will return a `404`. + +```python +from supertokens_python import get_all_cors_headers +from flask import Flask, abort +from flask_cors import CORS # type: ignore +from supertokens_python.framework.flask import Middleware + +app = Flask(__name__) +# highlight-next-line +Middleware(app) + +# TODO: Add APIs + +CORS( + app=app, + origins=[ + "^{form_websiteDomain}" + ], + supports_credentials=True, + # highlight-next-line + allow_headers=["Content-Type"] + get_all_cors_headers(), +) + +# This is required since if this is not there, then OPTIONS requests for +# the APIs exposed by the supertokens' Middleware will return a 404 +# highlight-start +@app.route('/', defaults={'u_path': ''}) # type: ignore +@app.route('/') # type: ignore +def catch_all(u_path: str): + abort(404) +# highlight-end + +# TODO: start server +``` + + + + +Use the `Middleware` and the `get_all_cors_headers()` functions as shown below in your `settings.py`. + +```python +from supertokens_python import get_all_cors_headers +from typing import List +from corsheaders.defaults import default_headers + +CORS_ORIGIN_WHITELIST = [ + "^{form_websiteDomain}" +] + +CORS_ALLOW_CREDENTIALS = True + +CORS_ALLOWED_ORIGINS = [ + "^{form_websiteDomain}" +] + +CORS_ALLOW_HEADERS: List[str] = list(default_headers) + [ + "Content-Type" + # highlight-next-line +] + get_all_cors_headers() + +INSTALLED_APPS = [ + 'corsheaders', + 'supertokens_python' +] + +MIDDLEWARE = [ + 'corsheaders.middleware.CorsMiddleware', + ..., + # highlight-next-line + 'supertokens_python.framework.django.django_middleware.middleware', +] +# TODO: start server +``` + + + + + + + + + + + +You can review all the endpoints that are added through the use of **SuperTokens** by visiting the [API Specs](https://app.swaggerhub.com/apis/supertokens/FDI). + + + + + +## 5. Add the SuperTokens Error Handler + + + + + + + +Depending on the language and framework that you are using, you might need to add a custom error handler to your server. +The handler will catch all the authentication related errors and return proper HTTP responses that can be parsed by the frontend SDKs. + + + + + + +```tsx +import express, { Request, Response, NextFunction } from 'express'; +import { errorHandler } from "supertokens-node/framework/express"; + +let app = express(); + +// ...your API routes + +// highlight-start +// Add this AFTER all your routes +app.use(errorHandler()) +// highlight-end + +// your own error handler +app.use((err: unknown, req: Request, res: Response, next: NextFunction) => { /* ... */ }); +``` + + + +No additional `errorHandler` is required. + + + + +Add the `errorHandler` **Before all your routes and plugin registration** + +```tsx +import Fastify from "fastify"; +import { errorHandler } from "supertokens-node/framework/fastify"; + +let fastify = Fastify(); + +// highlight-next-line +fastify.setErrorHandler(errorHandler()); + +// ...your API routes +``` + + + +No additional `errorHandler` is required. + + + + +No additional `errorHandler` is required. + + + + + + +:::info +You can skip this step +::: + + + + +:::info +You can skip this step +::: + + + + + + +## 6. Secure Application Routes + +Now that your server can authenticate users, the final step that you need to take care of is to prevent unauthorized access to certain parts of the application. + + + + +For your APIs that require a user to be logged in, use the `verifySession` middleware. + + + + + +```tsx +import express from "express"; +import { verifySession } from "supertokens-node/recipe/session/framework/express"; +import { SessionRequest } from "supertokens-node/framework/express"; + +let app = express(); + +// highlight-start +app.post("/like-comment", verifySession(), (req: SessionRequest, res) => { + let userId = req.session!.getUserId(); + // highlight-end + //.... +}); +``` + + + + +```tsx +import Hapi from "@hapi/hapi"; +import { verifySession } from "supertokens-node/recipe/session/framework/hapi"; +import { SessionRequest } from "supertokens-node/framework/hapi"; + +let server = Hapi.server({ port: 8000 }); + +server.route({ + path: "/like-comment", + method: "post", + //highlight-start + options: { + pre: [ + { + method: verifySession() + }, + ], + }, + handler: async (req: SessionRequest, res) => { + let userId = req.session!.getUserId(); + //highlight-end + //... + } +}) +``` + + + +```tsx +import Fastify from "fastify"; +import { verifySession } from "supertokens-node/recipe/session/framework/fastify"; +import { SessionRequest } from "supertokens-node/framework/fastify"; + +let fastify = Fastify(); + +//highlight-start +fastify.post("/like-comment", { + preHandler: verifySession(), +}, (req: SessionRequest, res) => { + let userId = req.session!.getUserId(); + //highlight-end + //.... +}); +``` + + + + +```tsx +import KoaRouter from "koa-router"; +import { verifySession } from "supertokens-node/recipe/session/framework/koa"; +import { SessionContext } from "supertokens-node/framework/koa"; + +let router = new KoaRouter(); + +//highlight-start +router.post("/like-comment", verifySession(), (ctx: SessionContext, next) => { + let userId = ctx.session!.getUserId(); + //highlight-end + //.... +}); +``` + + + + +```tsx +import { inject, intercept } from "@loopback/core"; +import { RestBindings, MiddlewareContext, post, response } from "@loopback/rest"; +import { verifySession } from "supertokens-node/recipe/session/framework/loopback"; +import { SessionContext } from "supertokens-node/framework/loopback"; + +class LikeComment { + //highlight-start + constructor(@inject(RestBindings.Http.CONTEXT) private ctx: MiddlewareContext) { } + @post("/like-comment") + @intercept(verifySession()) + @response(200) + handler() { + let userId = (this.ctx as SessionContext).session!.getUserId(); + //highlight-end + //.... + } +} +``` + + + + + + +For your APIs that require a user to be logged in, use the `VerifySession` middleware. + + + + +```go +import ( + "fmt" + "net/http" + + "github.com/supertokens/supertokens-golang/recipe/session" +) + +func main() { + _ = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + // Wrap the API handler in session.VerifySession + session.VerifySession(nil, likeCommentAPI).ServeHTTP(rw, r) + }) +} + +func likeCommentAPI(w http.ResponseWriter, r *http.Request) { + // retrieve the session object as shown below + sessionContainer := session.GetSessionFromRequestContext(r.Context()) + + userID := sessionContainer.GetUserID() + + fmt.Println(userID) +} +``` + + + + +```go +import ( + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/supertokens/supertokens-golang/recipe/session" + "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" +) + +func main() { + router := gin.New() + + // Wrap the API handler in session.VerifySession + router.POST("/likecomment", verifySession(nil), likeCommentAPI) +} + +// This is a function that wraps the supertokens verification function +// to work the gin +func verifySession(options *sessmodels.VerifySessionOptions) gin.HandlerFunc { + return func(c *gin.Context) { + session.VerifySession(options, func(rw http.ResponseWriter, r *http.Request) { + c.Request = c.Request.WithContext(r.Context()) + c.Next() + })(c.Writer, c.Request) + // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly + c.Abort() + } +} + +func likeCommentAPI(c *gin.Context) { + // retrieve the session object as shown below + sessionContainer := session.GetSessionFromRequestContext(c.Request.Context()) + + userID := sessionContainer.GetUserID() + + fmt.Println(userID) +} +``` + + + + +```go +import ( + "fmt" + "net/http" + + "github.com/go-chi/chi" + "github.com/supertokens/supertokens-golang/recipe/session" +) + +func main() { + r := chi.NewRouter() + + // Wrap the API handler in session.VerifySession + r.Post("/likecomment", session.VerifySession(nil, likeCommentAPI)) +} + +func likeCommentAPI(w http.ResponseWriter, r *http.Request) { + // retrieve the session object as shown below + sessionContainer := session.GetSessionFromRequestContext(r.Context()) + + userID := sessionContainer.GetUserID() + + fmt.Println(userID) +} +``` + + + + +```go +import ( + "fmt" + "net/http" + + "github.com/gorilla/mux" + "github.com/supertokens/supertokens-golang/recipe/session" +) + +func main() { + router := mux.NewRouter() + + // Wrap the API handler in session.VerifySession + router.HandleFunc("/likecomment", session.VerifySession(nil, likeCommentAPI)).Methods(http.MethodPost) +} + +func likeCommentAPI(w http.ResponseWriter, r *http.Request) { + // retrieve the session object as shown below + sessionContainer := session.GetSessionFromRequestContext(r.Context()) + + userID := sessionContainer.GetUserID() + + fmt.Println(userID) +} +``` + + + + + + +For your APIs that require a user to be logged in, use the `verify_session` middleware. + + + + +```python +from supertokens_python.recipe.session.framework.fastapi import verify_session +from supertokens_python.recipe.session import SessionContainer +from fastapi import Depends + +# highlight-start +@app.post('/like_comment') # type: ignore +async def like_comment(session: SessionContainer = Depends(verify_session())): + user_id = session.get_user_id() + # highlight-end + + print(user_id) +``` + + + + +```python +from supertokens_python.recipe.session.framework.flask import verify_session +from supertokens_python.recipe.session import SessionContainer +from flask import g + +# highlight-start +@app.route('/update-jwt', methods=['POST']) # type: ignore +@verify_session() +def like_comment(): + session: SessionContainer = g.supertokens # type: ignore + + user_id = session.get_user_id() + # highlight-end + + print(user_id) +``` + + + + +```python +from supertokens_python.recipe.session.framework.django.asyncio import verify_session +from django.http import HttpRequest +from supertokens_python.recipe.session import SessionContainer + +# highlight-start +@verify_session() +async def like_comment(request: HttpRequest): + session: SessionContainer = request.supertokens # type: ignore + + user_id = session.get_user_id() + # highlight-end + + print(user_id) +``` + + + + + + + +The middleware function returns a `401` to the frontend if a session doesn't exist, or if the access token has expired, in which case, our frontend SDK automatically refreshes the session. + +In case of successful session verification, you get access to a `session` object using which you can get the user's ID, or manipulate the session information. + +## 7. Test the Login Flow + +Now that you have configured both the frontend and the backend, you can return to the frontend login page. +From here follow these steps to confirm that your setup is working properly. +- Click on the **Sign up** button to create a new account. +- After you have created the account go to **Login** page and fill in your credentials. +- If you are greeted with the login screen you have completed the quickstart setup. + +:::success 🎉 Congratulations 🎉 + +You've successfully integrated **SuperTokens** with your existing application! + +Of course, there are additional things that you should add in order to provide a complete authentication experience. +We will talk about those things in the [next section](./next-steps). + +::: + diff --git a/v2/thirdpartypasswordless/quickstart/frontend-setup.mdx b/v2/thirdpartypasswordless/quickstart/frontend-setup.mdx new file mode 100644 index 000000000..63bce28f9 --- /dev/null +++ b/v2/thirdpartypasswordless/quickstart/frontend-setup.mdx @@ -0,0 +1,955 @@ +--- +id: frontend-setup +title: Frontend Setup +hide_title: true +show_ui_switcher: true +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" +import WebJsInjector from "/src/components/webJsInjector" + +import FrontendCustomUISDKInstall from '../../community/reusableMD/custom-ui/frontent-custom-ui-sdk-install.mdx' +import FrontendCustomUISessionTokens from '../../community/reusableMD/custom-ui/frontend-custom-ui-session-tokens.mdx' +import FrontendCustomUISessionManagement from '../../community/reusableMD/custom-ui/frontend-custom-ui-session-management.mdx' +import FrontendCustomUISignout from '../../community/reusableMD/custom-ui/frontend-custom-ui-signout.mdx' +import FrontendCustomUIThirdParty from '../../community/reusableMD/custom-ui/frontend-custom-ui-thirdparty.mdx' +import FrontendCustomUIPasswordless from '../../community/reusableMD/custom-ui/frontend-custom-ui-passwordless.mdx' +import FrontendSDKInstall from "../../community/reusableMD/frontend-sdk-install.mdx" + +import {CustomUILink, PrebuiltUILink, PreBuiltOrCustomUISwitcher, PreBuiltUIContent, CustomUIContent} from "/src/components/preBuiltOrCustomUISwitcher" +import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" +import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" +import PreBuiltUIInjector from "/src/components/prebuiltuiInjector" +import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; +import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" +import {Question, Answer}from "/src/components/question" +import RRDVersionSubTabs from "/src/components/tabs/RRDVersionSubTabs" +import AppInfoForm from "/src/components/appInfoForm" +import NodeJSFrameworkSubTabsServerless from "/src/components/tabs/NodeJSFrameworkSubTabsServerless" +import NpmVersionOrYarnSubTabs from "/src/components/tabs/NpmVersionOrYarnSubTabs" +import CoreInjector from "/src/components/coreInjector" +import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs" +import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" +import BackendSDKCasing from "/src/components/BackendSDKCasing" + +import { PasswordlessFrontendForm } from "/src/components/snippetConfigForm/passwordlessFrontendForm"; + + +# Frontend Setup + +Start the setup by configuring your frontend application to use **SuperTokens** for authentication. + + + + + +This guide uses the **SuperTokens Pre Built UI** components. +If you want to create your own interface please check the **Custom UI** tutorial. + +## 1. Install the SDK + + + + + +Run the following command in your terminal to install the package. + + + + + +## 2. Initialize the SDK + + + + + + + + + + + +In your main application file call the `SuperTokens.init` function to initialize the SDK. +The `init` call includes the [main configuration details](../appinfo), as well as the **recipes** that you will be using in your setup. +After that you will have to wrap the application with the `SuperTokensWrapper` component. +This will provide authentication context for the rest of the UI tree. + + + +You also have to specify which `contact method` should be used for the `passwordless` flow. +Just click one of the options from the next form and the code snippet will get updated. + + + +```tsx +import React from 'react'; + +// highlight-start +import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; +import Passwordless from "supertokens-auth-react/recipe/passwordless"; +import ThirdParty from "supertokens-auth-react/recipe/thirdparty"; +import Session from "supertokens-auth-react/recipe/session"; + +SuperTokens.init({ + appInfo: { + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + websiteDomain: "^{form_websiteDomain}", + apiBasePath: "^{form_apiBasePath}", + websiteBasePath: "^{form_websiteBasePath}" + }, + recipeList: [ + Passwordless.init({ + contactMethod: "^{form_contactMethod}", + }), + ThirdParty.init({ + signInAndUpFeature: { + providers: [ + ThirdParty.Github.init(), + ThirdParty.Google.init(), + ThirdParty.Facebook.init(), + ThirdParty.Apple.init(), + ], + } + }), + Session.init() + ] +}); +// highlight-end + + +/* Your App */ +class App extends React.Component { + render() { + return ( + // highlight-next-line + + {/*Your app components*/} + // highlight-next-line + + ); + } +} +``` + + + + + + + + + + +```tsx +import SuperTokens from 'supertokens-react-native'; + +SuperTokens.init({ + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}" +}); +``` + + + + + + + + + + + +Specify which `contact method` should be used for the `passwordless` flow. +Just click one of the options from the next form and the code snippet will get updated. + + + +Before we initialize the `supertokens-web-js` SDK let's see how we will use it in our Angular app + +**Architecture** + +- The `supertokens-web-js` SDK is responsible for session management and providing helper functions to check if a session exists, or validate the access token claims on the frontend (for example, to check for user roles before showing some UI). We will initialise this SDK on the root of your Angular app, so that all pages in your app can use it. +- We will create a `^{form_websiteBasePath}*` route in the Angular app which will render our pre built UI which will also need to be initialised, but only on that route. + +**Creating the `^{form_websiteBasePath}` route** + +- Use the Angular CLI to generate a new route + + ```bash + ng generate module auth --route auth --module app.module + ``` + +- Add the following code to your `auth` angular component + + ```tsx title="/app/auth/auth.component.ts" + import {init as supertokensUIInit} from "supertokens-auth-react-script"; + import supertokensUIPasswordless from "supertokens-auth-react-script/recipe/passwordless"; + import supertokensUIThirdParty from "supertokens-auth-react-script/recipe/thirdparty"; + import supertokensUISession from "supertokens-auth-react-script/recipe/session"; + 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: [ + supertokensUIPasswordless.init({ + contactMethod: "^{form_contactMethod}" + }), + supertokensUIThirdParty.init({ + signInAndUpFeature: { + providers: [ + supertokensUIThirdParty.Github.init(), + supertokensUIThirdParty.Google.init(), + supertokensUIThirdParty.Facebook.init(), + supertokensUIThirdParty.Apple.init(), + ] + } + }), + supertokensUISession.init(), + ], + }); + } + this.renderer.appendChild(this.document.body, script); + } + } + ``` + - In the `loadScript` function, we provide the SuperTokens config for the UI. We add the passwordless, session and social login recipes along with Github, Google, Facebook and Apple login buttons. + +- Initialize the `supertokens-web-js` SDK in your angular app's root component. This will provide session management across your entire application. + + ```tsx title="/app/app.component.ts " + import SuperTokens from "supertokens-web-js"; + import Session from "supertokens-web-js/recipe/session"; + + SuperTokens.init({ + appInfo: { + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}", + }, + recipeList: [ + Session.init(), + ], + }); + ``` + +
      + +
      + +
      + +
      + + + + + + + +Specify which `contact method` should be used for the `passwordless` flow. +Just click one of the options from the next form and the code snippet will get updated. + + + +Before we initialize the `supertokens-web-js` SDK let's see how we will use it in our Vue app + +**Architecture** + +- The `supertokens-web-js` SDK is responsible for session management and providing helper functions to check if a session exists, or validate the access token claims on the frontend (for example, to check for user roles before showing some UI). We will initialise this SDK on the root of your Vue app, so that all pages in your app can use it. +- We will create a `^{form_websiteBasePath}*` route in the Vue app which will render our pre built UI which will also need to be initialised, but only on that route. + +**Creating the `^{form_websiteBasePath}` route** + +- Create a new file `AuthView.vue`, this Vue component will be used to render the auth component: + ```tsx + import {init as supertokensUIInit} from "supertokens-auth-react-script"; + import supertokensUIPasswordless from "supertokens-auth-react-script/recipe/passwordless"; + import supertokensUIThirdParty from "supertokens-auth-react-script/recipe/thirdparty"; + import supertokensUISession from "supertokens-auth-react-script/recipe/session"; + + + + ``` + - In the `loadScript` function, we provide the SuperTokens config for the UI. We add the passwordless, session and social login recipes along with Github, Google, Facebook and Apple login buttons. + +- Initialize the `supertokens-web-js` SDK in your Vue app's `main.ts` file. This will provide session management across your entire application. + + ```tsx title="/main.ts " + // @ts-ignore + import { createApp } from "vue"; + import SuperTokens from "supertokens-web-js"; + import Session from "supertokens-web-js/recipe/session"; + // @ts-ignore + import App from "./App.vue"; + // @ts-ignore + import router from "./router"; + + SuperTokens.init({ + appInfo: { + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}", + }, + recipeList: [ + Session.init(), + ], + }); + + const app = createApp(App); + + app.use(router); + + app.mount("#app"); + + ``` + + + + + + + + + +:::important +SuperTokens does not support pre-built UI for mobile frameworks. Please toggle the **Custom UI** option from the *Table of Contents* section. +::: + + + +
      + +## 3. Configure Routing + + + + + + + + +In order for the **Pre Built UI** to be rendered inside your application, will will have to specify which routes will show the authentication components. +The **React SDK** uses [**React Router**](https://reactrouter.com/en/main) under the hood to achieve this. +Based on whether you already use this package or not in your project, there are two different ways of configuring the routes. + + + + + + + + + +Call the `getSuperTokensRoutesForReactRouterDom` method from within any `react-router-dom` `Routes` component. + +```tsx +import React from 'react'; +import { + BrowserRouter, + Routes, + Route, + Link +} from "react-router-dom"; + +// highlight-next-line +import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; +import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui"; +^{prebuiltuiimport} +import * as reactRouterDom from "react-router-dom"; + +class App extends React.Component { + render() { + return ( + + + + {/*This renders the login UI on the ^{form_websiteBasePath} route*/} + // highlight-next-line + {getSuperTokensRoutesForReactRouterDom(reactRouterDom, [^{recipePreBuiltUINameCapitalLetters}])} + {/*Your app routes*/} + + + + ); + } +} +``` + + + + + +Call the `getSuperTokensRoutesForReactRouterDom` method from within any `react-router-dom` `Switch` component. + +```tsx +import React from 'react'; +import { + BrowserRouter, + Switch, + Route, + Link +} from "react-router-dom5"; + +// highlight-next-line +import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; +import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui"; +^{prebuiltuiimport} +import * as reactRouterDom from "react-router-dom"; + + +class App extends React.Component { + render() { + return ( + + + + {/*This renders the login UI on the ^{form_websiteBasePath} route*/} + // highlight-next-line + {getSuperTokensRoutesForReactRouterDom(reactRouterDom, [^{recipePreBuiltUINameCapitalLetters}])} + {/*Your app routes*/} + + + + ); + } +} +``` + + + + + + + + +Add the highlighted code snippet to your root level `render` function. + +```tsx +import React from 'react'; +^{prebuiltuiimport} +import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; +import { canHandleRoute, getRoutingComponent } from "supertokens-auth-react/ui"; + +class App extends React.Component { + render() { + // highlight-start + if (canHandleRoute([^{recipePreBuiltUINameCapitalLetters}])) { + // This renders the login UI on the ^{form_websiteBasePath} route + return getRoutingComponent([^{recipePreBuiltUINameCapitalLetters}]) + } + // highlight-end + + return ( + {/*Your app*/} + ); + } + +} +``` + + + + + + + + + + +Update your angular router so that all auth related requests load the `auth` component + +```tsx title="/app/app-routing.module.ts" +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +const routes: Routes = [ + // highlight-start + { + path: "^{form_websiteBasePath_withoutForwardSlash}", + // @ts-ignore + loadChildren: () => import("./auth/auth.module").then((m) => m.AuthModule), + }, + + // @ts-ignore + { path: "**", loadChildren: () => import("./home/home.module").then((m) => m.HomeModule) }, + // highlight-end +]; + +@NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule], +}) +export class AppRoutingModule {} +``` + + + + + + + + +Update your Vue router so that all auth related requests load the `AuthView` component + +```tsx title="/router/index.ts" +// @ts-ignore +import { createRouter, createWebHistory } from "vue-router"; +// @ts-ignore +import HomeView from "../views/HomeView.vue"; +// @ts-ignore +import AuthView from "../views/AuthView.vue"; + +const router = createRouter({ + // @ts-ignore + history: createWebHistory(import.meta.env.BASE_URL), + routes: [ + { + path: "/", + name: "home", + component: HomeView, + }, + { + path: "^{form_websiteBasePath}/:pathMatch(.*)*", + name: "auth", + component: AuthView, + }, + ], +}); + +export default router; +``` + + + + + + + +:::important +SuperTokens does not support pre-built UI for mobile frameworks. Please toggle the **Custom UI** option from the *Table of Contents* section. +::: + + + + + + + +## 4. Handle Session Tokens + + + + + +This part is handled automatically by the **Frontend SDK**. +You don not need to do anything. +The step serves more as a way for us to tell you how is this handled under the hood. + +After you call the `init` function, the **SDK** will add interceptors to both `fetch` and `XHR`, XMLHTTPRequest. The latter is used by the `axios` library. +The interceptors save the session tokens that are generated from the authentication flow. +Those tokens are then added to requests initialized by your frontend app which target the backend API. +By default, the tokens are stored through session cookies but you can also switch to [header based authentication](../common-customizations/sessions/token-transfer-method). + + + +## 5. Secure Application Routes + +In order to prevent unauthorized access to ceratain parts of your frontend application you can use our utilities. +Follow the code samples below to understand how to do this. + + + + + +You can wrap your components with the `` react component. This will ensure that your component renders only if the user is logged in. If they are not logged in, the user will be redirected to the login page. + +```tsx +import React from "react"; +import { + BrowserRouter, + Routes, + Route, +} from "react-router-dom"; +// highlight-next-line +import { SessionAuth } from "supertokens-auth-react/recipe/session"; +// @ts-ignore +import MyDashboardComponent from "./dashboard"; + +class App extends React.Component { + render() { + return ( + + + + {/*Components that require to be protected by authentication*/} + + + // highlight-end + } /> + + + ); + } +} +``` + + + + + +You can use the `doesSessionExist` function to check if a session exists in all your routes. + +```tsx +import Session from 'supertokens-web-js/recipe/session'; + +async function doesSessionExist() { + if (await Session.doesSessionExist()) { + // user is logged in + } else { + // user has not logged in yet + } +} +``` + + + + + +## 6. View the login UI + + + +You can check the login UI by visiting the `^{form_websiteBasePath}` route, in your frontend application. +To review all the components of our pre-built UI please follow [this link](https://master--6571be2867f75556541fde98.chromatic.com/?path=/story/auth-page--playground). + + + + + +
      + + + +This guide shows you how to create your own UI on top of the **SuperTokens SDK**. +If you want to use our **Pre Built Components** please check the following tutorial. + +## 1. Install the SDK + + + +## 2. Initialize the SDK + +Call the SDK init function at the start of your application. +The invocation includes the [main configuration details](../appinfo), as well as the **recipes** that you will be using in your setup. + + + + + + + + + +```tsx +import SuperTokens from 'supertokens-web-js'; +import Session from 'supertokens-web-js/recipe/session'; +import ^{recipeNameCapitalLetters} from 'supertokens-web-js/recipe/^{codeImportRecipeName}' + +SuperTokens.init({ + appInfo: { + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}", + appName: "...", + }, + recipeList: [ + Session.init(), + ^{recipeNameCapitalLetters}.init(), + ], +}); +``` + + + + + + + + + +```tsx +import supertokens from "supertokens-web-js-script"; +import supertokensSession from "supertokens-web-js-script/recipe/session"; +import supertokens^{recipeNameCapitalLetters} from 'supertokens-web-js-script/recipe/^{codeImportRecipeName}' +supertokens.init({ + appInfo: { + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}", + appName: "...", + }, + recipeList: [ + supertokensSession.init(), + supertokens^{recipeNameCapitalLetters}.init(), + ], +}); +``` + + + + + + + + + + + + + + + + + +```tsx +import SuperTokens from 'supertokens-react-native'; + +SuperTokens.init({ + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}", +}); +``` + + + + + + + + + +Add the `SuperTokens.init` function call at the start of your application. + +```kotlin +import android.app.Application +import com.supertokens.session.SuperTokens + +class MainApplication: Application() { + override fun onCreate() { + super.onCreate() + + SuperTokens.Builder(this, "^{form_apiDomain}") + .apiBasePath("^{form_apiBasePath}") + .build() + } +} +``` + + + + + + + + + + +```swift +import UIKit +import SuperTokensIOS + +fileprivate class ApplicationDelegate: UIResponder, UIApplicationDelegate { + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + do { + try SuperTokens.initialize( + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}" + ) + } catch SuperTokensError.initError(let message) { + // TODO: Handle initialization error + } catch { + // Some other error + } + + return true + } + +} +``` + + + + + + + + + +```dart +import 'package:supertokens_flutter/supertokens.dart'; + +void main() { + SuperTokens.init( + apiDomain: "^{form_apiDomain}", + apiBasePath: "^{form_apiBasePath}", + ); +} +``` + + + + + + + + + + + + +## 3. Add the Login UI for the Passwordless Flow + + + +## 4. Add the Login UI for the ThirdParty Flow + + + +## 5. Handle Session Tokens + + + +## 6. Protect Frontend Routes + + + +## 7. Add a Sign Out Action + + + + + + + +
      + + +:::success 🎉 Congratulations 🎉 + +Congratulations! You've successfully integrated your frontend app with SuperTokens. + +The [next section](./backend-setup) will guide you through setting up your backend and then you should be able to complete a login flow. + +::: + diff --git a/v2/thirdpartypasswordless/quickstart/introduction.mdx b/v2/thirdpartypasswordless/quickstart/introduction.mdx new file mode 100644 index 000000000..68de22862 --- /dev/null +++ b/v2/thirdpartypasswordless/quickstart/introduction.mdx @@ -0,0 +1,85 @@ +--- +id: introduction +title: Introduction +hide_title: true +show_ui_switcher: false +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + +# Introduction + +## Overview + +This quickstart will guide you through how to set up a basic project that uses **SuperTokens** to authenticate users. +The tutorial shows both a **Passwordless** and a **ThirdParty** login flow, rendered by either our **Prebuilt UI components** or by your own **Custom UI**. + + + + + +If you want to skip straight to an example application you can choose between: +- Checking our live [demo application](https://^{docsLinkRecipeName}.demo.supertokens.com/auth) +- Running a **SuperTokens** project from your local machine. You just have to use our CLI app and execute the following command: + +```bash +npx create-supertokens-app@latest --recipe=^{docsLinkRecipeName} +``` + + + + +## Before you start + + + + + +Before going into the actual tutorial, let's get a clear picture of how **SuperTokens** works and some of the terms that we will use throughout the documentation. + +### SuperTokens Core + +The main service that provides all the functionality is called the **SuperTokens Core**. SDKs communicate over an API with this service in order to +perform authentication related tasks. + +Unlike with other providers, the **SuperTokens Frontend SDK** never talks to the **Authentication Service** directly. +All the requests target your existing **Backend Service**. +From there, the **Backend SDKs** are used to expose new authentication routes. Those in turn communicate with the **SuperTokens Core**. + +You can check the following diagram for a high level overview of how the services will interact within an authentication setup that involves **SuperTokens**. + + + + Flowchart of architecture when using SuperTokens managed service + + + Flowchart of architecture when self-hosting SuperTokens + + + +:::info Edge Cases +- You can also host the **SuperTokens Core** yourself. In that case your backend will communicate with a service that exists inside your infrastructure. +- If you are using a backend for which we do not have an SDK, you will have to spin up an additional auth service in a language for which we do have a backend SDK (NodeJS, Python or Golang). +::: + + +### Recipes + +The functionalities that **SuperTokens** provides are bundled into objects that can be reffered to as **Recipes**. +Everything from *authentication methods* to *session and user management* can be included under this concept. +In the following sections, we will see how recipes get initialised and configured and how you can customise them to fit your use case. + + +Now that we have cleared all this out, we can move forward with the actual tutorial. +Go to the next page to see how to configure your [Frontend Application](./frontend-setup). + + + diff --git a/v2/thirdpartypasswordless/quickstart/next-steps.mdx b/v2/thirdpartypasswordless/quickstart/next-steps.mdx new file mode 100644 index 000000000..ddf50b09c --- /dev/null +++ b/v2/thirdpartypasswordless/quickstart/next-steps.mdx @@ -0,0 +1,178 @@ +--- +id: next-steps +title: Next Steps +hide_title: true +show_ui_switcher: false +show_next_button: false +--- + +import Card from "/src/components/card/Card" +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +import PreBuiltUIInjector from "/src/components/prebuiltuiInjector" +import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; +import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" +import {Question, Answer}from "/src/components/question" +import RRDVersionSubTabs from "/src/components/tabs/RRDVersionSubTabs" +import AppInfoForm from "/src/components/appInfoForm" +import NodeJSFrameworkSubTabsServerless from "/src/components/tabs/NodeJSFrameworkSubTabsServerless" +import CoreInjector from "/src/components/coreInjector" +import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs" +import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" +import BackendSDKCasing from "/src/components/BackendSDKCasing" + + + +# Next Steps + + + + + +## Overview + +Now that you have completed the quickstart guide there are a few more things that you need to take care of on the road towards a production ready authentication experience. + +## Configure the Core Service + +If you have signed up and deployed a SuperTokens environment already, you can skip this step. +Otherwise, please follow these instructions to use the correct **SuperTokens Core** instance in your application. + +The steps show you how to connect to a **SuperTokens Managed Service Environment**. +If you want to self host the core instance please check the [following guide](../pre-built-ui/setup/core/with-docker). + +### 1. Sign up for a SuperTokens account + +Open this [page](https://supertokens.com/auth) in order to access the account creation page. +Select the account that you want to use and wait for the action to complete. + +### 2. Select the authentication method + +After you have created your account, you will be prompted with a form that will ask you to specify details about your configuration. +Select which authentication method that you want to use. + +Integration with SuperTokens SDKs + +### 3. Select the region where you want to deploy the core service# + +SuperTokens environments can be deployed in 3 different regions: `US East (N. Virginia)`, `Europe (Ireland)`, `Asia Pacific (Singapore)`. +In order to avoid any latency issues please select a region that is closest to where your services are hosted. + +### 4. Click the deploy button 🚀 + +Integration with SuperTokens SDKs + +Our internal service will deploy a separate environment based on your selection. + +After this process is complete, you will be directed to the dashboard page. Here you can view and edit information about your newly created environment. + +The main thing that we want to focus is the **Connecting to a development instance** section. +There you can see two different values, the `connectionURI` and the `apiKey`. +You will use these values in the next step. + +:::info + +The initial setup flow only configures a development environment. In order to use SuperTokens in production, you will have to click the Create Production Env button. + +::: + +### 5. Connect the Backend SDK with SuperTokens 🔌 + +Add the `connectionURI` and the `apiKey` shown on the dashboard to your code on the backend. + +SuperTokens managed service dashboard connectionURI and API key + + + + +```tsx +import supertokens from "supertokens-node"; + +supertokens.init({ + // highlight-start + supertokens: { + connectionURI: "", + apiKey: "" + }, + // highlight-end + appInfo: { + apiDomain: "...", + appName: "...", + websiteDomain: "..." + }, + recipeList: [] +}); +``` + + + + +```go +import "github.com/supertokens/supertokens-golang/supertokens" + +func main() { + supertokens.Init(supertokens.TypeInput{ + // highlight-start + Supertokens: &supertokens.ConnectionInfo{ + ConnectionURI: "", + APIKey: "", + }, + // highlight-end + }) +} + +``` + + + + +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig + +init( + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + # highlight-start + supertokens_config=SupertokensConfig( + connection_uri='', + api_key='' + ), + # highlight-end + framework='...', # type: ignore + recipe_list=[ + #... + ] +) +``` + + + + + + + +## Customize Your Authentication Flow + +After you have connected the Backend SDK to a specific **SuperTokens Core Service**, you can go check the rest of the documentation. +There are several sections that show you how to customize the authentication experience to fit your specific needs. +Some of the most common subjects are: + +- [**Add Email Verification**](../common-customizations/email-verification/about) +- [**Add a Custom Redirect Action**](../pre-built-ui/auth-redirection) +- [**Use Custom Session Management**](../common-customizations/sessions/session-verification-in-api/get-session) +- [**Share Sessions Across Subdomains**](../common-customizations/sessions/share-sessions-across-sub-domains) +- [**Post Sign In Actions**](../common-customizations/handling-signinup-success) + + +## Explore Additional Features + +You can also review the additional features that **SuperTokens** exposes. +Those can help you extend the authentication implementation on different levels. + +- [**Self Host SuperTokens Core**](../pre-built-ui/setup/core/with-docker) +- [**Migration Guide**](../migration/about) +- [**Multi Factor Authentication**](../../mfa/introduction) +- [**Manage Users through the User Management Dashboard**](../pre-built-ui/setup/user-management-dashboard/setup) +- [**Multi Tenancy**](../../multitenancy/introduction) +- [**User Roles and Permissions**](../user-roles/initialisation) + diff --git a/v2/thirdpartypasswordless/sidebars.js b/v2/thirdpartypasswordless/sidebars.js index 13bfe6f0a..5869afc35 100644 --- a/v2/thirdpartypasswordless/sidebars.js +++ b/v2/thirdpartypasswordless/sidebars.js @@ -1,162 +1,35 @@ module.exports = { sidebar: [ { + label: "quickstart", type: "category", - label: "Start Here", customProps: { highlightGroup: true, }, collapsed: false, items: [ - "introduction", { - type: "category", - label: "Quick setup with Pre built UI", - customProps: { - categoryIcon: "lightning", - }, - items: [ - { - type: "category", - label: "Setup", - collapsed: false, - items: [ - "pre-built-ui/setup/frontend", - "pre-built-ui/setup/backend", - { - type: "category", - label: "Step 3: Core", - items: [ - { - type: "category", - label: "Self hosted", - items: [ - "pre-built-ui/setup/core/with-docker", - "pre-built-ui/setup/core/without-docker", - { - type: "category", - label: "Database Setup", - items: [ - "pre-built-ui/setup/database-setup/mysql", - "pre-built-ui/setup/database-setup/postgresql", - "pre-built-ui/setup/database-setup/rename-database-tables", - ], - }, - ], - }, - "pre-built-ui/setup/core/saas-setup", - ], - }, - { - type: "category", - label: "Step 4: User management dashboard", - items: [ - "pre-built-ui/setup/user-management-dashboard/setup", - "pre-built-ui/setup/user-management-dashboard/users-listing-and-details", - "pre-built-ui/setup/user-management-dashboard/managing-user-roles-and-permissions", - { - type: "category", - label: "Tenant Management", - collapsed: true, - items: [ - "pre-built-ui/setup/user-management-dashboard/tenant-management/overview", - "pre-built-ui/setup/user-management-dashboard/tenant-management/details", - "pre-built-ui/setup/user-management-dashboard/tenant-management/third-party", - ], - }, - ], - }, - ], - }, - "pre-built-ui/handling-session-tokens", - "pre-built-ui/securing-routes", - "pre-built-ui/sign-out", - "pre-built-ui/auth-redirection", - "pre-built-ui/enable-email-verification", - "pre-built-ui/multitenant-login", - { - type: "category", - label: "Further Reading", - items: [ - "pre-built-ui/further-reading/thirdparty-login", - "pre-built-ui/further-reading/passwordless-login", - "pre-built-ui/further-reading/email-verification", - ], - }, - ], + id: "quickstart/introduction", + type: "doc", + label: "Introduction", }, { - type: "category", - label: "Using your own UI / Custom UI", - customProps: { - categoryIcon: "pencil", - }, - items: [ - { - type: "category", - label: "Initialisation", - collapsed: false, - items: [ - "custom-ui/init/frontend", - "custom-ui/init/backend", - { - type: "category", - label: "Step 3: Core", - items: [ - { - type: "category", - label: "Self hosted", - items: [ - "custom-ui/init/core/with-docker", - "custom-ui/init/core/without-docker", - { - type: "category", - label: "Database Setup", - items: [ - "custom-ui/init/database-setup/mysql", - "custom-ui/init/database-setup/postgresql", - "custom-ui/init/database-setup/rename-database-tables", - ], - }, - ], - }, - "custom-ui/init/core/saas-setup", - ], - }, - { - type: "category", - label: "Step 4: User management dashboard", - items: [ - "custom-ui/init/user-management-dashboard/setup", - "custom-ui/init/user-management-dashboard/users-listing-and-details", - "custom-ui/init/user-management-dashboard/managing-user-roles-and-permissions", - { - type: "category", - label: "Tenant Management", - collapsed: true, - items: [ - "custom-ui/init/user-management-dashboard/tenant-management/overview", - "custom-ui/init/user-management-dashboard/tenant-management/details", - "custom-ui/init/user-management-dashboard/tenant-management/third-party", - ], - }, - ], - }, - ], - }, - "custom-ui/login-magic-link", - "custom-ui/login-otp", - "custom-ui/thirdparty-login", - "custom-ui/handling-session-tokens", - "custom-ui/securing-routes", - "custom-ui/sign-out", - "custom-ui/enable-email-verification", - "custom-ui/multitenant-login", - ], + id: "quickstart/frontend-setup", + type: "doc", + label: "Frontend Setup", + }, + { + id: "quickstart/backend-setup", + type: "doc", + label: "Backend Setup", + }, + { + id: "quickstart/next-steps", + type: "doc", + label: "Next Steps", }, ], }, - "user-object", { type: "category", label: "Integrations", @@ -376,6 +249,11 @@ module.exports = { "common-customizations/sessions/protecting-frontend-routes", "common-customizations/sessions/with-jwt/read-jwt", "common-customizations/sessions/ssr", + { + id: "pre-built-ui/handling-session-tokens", + type: "doc", + label: "Access Session Tokens", + }, { type: "category", label: "Reading / modifying session claims", @@ -384,6 +262,12 @@ module.exports = { "common-customizations/sessions/claims/claim-validators", ], }, + { id: "pre-built-ui/sign-out", type: "doc", label: "Add Sign Out" }, + { + id: "pre-built-ui/auth-redirection", + type: "doc", + label: "Add Redirect Actions", + }, "common-customizations/sessions/revoke-session", "common-customizations/sessions/anonymous-session", "common-customizations/sessions/user-impersonation", @@ -491,6 +375,7 @@ module.exports = { ], }, "common-customizations/generating-magic-link-manually", + "add-multiple-clients-for-the-same-provider", "common-customizations/get-user-info", "common-customizations/user-pagination", "common-customizations/delete-user", @@ -591,6 +476,28 @@ module.exports = { }, "common-customizations/multiple-clients", "common-customizations/userid-format", + { + id: "pre-built-ui/setup/core/saas-setup", + label: "Connecting to the SuperTokens Core Managed Service", + type: "doc", + }, + { + type: "category", + label: "Self Hosting SuperTokens Core", + items: [ + "pre-built-ui/setup/core/with-docker", + "pre-built-ui/setup/core/without-docker", + { + type: "category", + label: "Database Setup", + items: [ + "pre-built-ui/setup/database-setup/mysql", + "pre-built-ui/setup/database-setup/postgresql", + "pre-built-ui/setup/database-setup/rename-database-tables", + ], + }, + ], + }, { type: "category", label: @@ -722,6 +629,24 @@ module.exports = { "mfa", "multi-tenant", "attack-protection-suite", + { + type: "category", + label: "User Management dashboard", + items: [ + "pre-built-ui/setup/user-management-dashboard/setup", + "pre-built-ui/setup/user-management-dashboard/users-listing-and-details", + "pre-built-ui/setup/user-management-dashboard/managing-user-roles-and-permissions", + { + type: "category", + label: "Tenant Management", + collapsed: true, + items: [ + "pre-built-ui/setup/user-management-dashboard/tenant-management/overview", + "pre-built-ui/setup/user-management-dashboard/tenant-management/details", + ], + }, + ], + }, ], }, "scalability", @@ -761,12 +686,22 @@ module.exports = { label: "References", items: [ "architecture", + "user-object", "other-frameworks", "flow_diagram", "appinfo", "sdks", "apis", "compatibility-table", + { + type: "category", + label: "Prebuilt UI Components", + items: [ + "pre-built-ui/further-reading/thirdparty-login", + "pre-built-ui/further-reading/passwordless-login", + "pre-built-ui/further-reading/email-verification", + ], + }, ], }, ],