Setting up multiple frontends for one backend
Many a times, you have a setup where different frontend clients query the same backend. For example, this can happen in the case of a mobile app and a web app - both of these types of clients query the same backend. Another common use case is that you may have a common testing backend that is shared across local frontend development (on localhost
), and a staging / testing frontend (on test.yoursite.com
).
In all these cases, the location of the different frontend clients are different. In case of a mobile app, it can be a deep link to the app, in case of local development, it can be http://localhost:3000
, for staging, it can be https://test.yoursite.com
, etc. Not only is the domain different, but so is the protocol (http vs https).
All of these have an impact on the functioning of SuperTokens. For example, if your frontend is on localhost
and your API is api.example.com
, then the cookieSameSite
value for sessions should resolve to none
, whereas if the frontend is on test.example.com
, then the cookieSameSite
value should resolve to lax
. Another example is the effect on magic links, password reset or email verification links - the domains and protocols of these would need to change based on the frontend that is querying - if the query is coming from the localhost
site, then the link URL would be http://localhost...
, whereas if it's coming from the test site, it would be https://test.example.com...
.
#
Step 1: Define a dynamic origin on the backendIn order to facilitate this, we allow you to configure an origin
function on the backend which takes in the request object, and returns a string representing the domain of the request:
- NodeJS
- GoLang
- Python
- Other Frameworks
Important
import supertokens from "supertokens-node";
supertokens.init({
appInfo: {
appName: "...",
apiDomain: "...",
origin: (input) => {
if (input.request !== undefined) {
let origin = input.request.getHeaderValue("origin");
if (origin === undefined) {
// this means the client is in an iframe, it's a mobile app, or
// there is a privacy setting on the frontend which doesn't send
// the origin
} else {
if (origin === "https://test.example.com") {
// query from the test site
return "https://test.example.com";
} else if (origin === "http://localhost:3000") {
// query from local development
return "http://localhost:3000";
}
}
}
// in case the origin is unknown or not set, we return a default
// value which will be used for this request.
return "https://test.example.com";
}
},
supertokens: {
connectionURI: "...",
},
recipeList: [/* ... */]
})
import (
"net/http"
"github.com/supertokens/supertokens-golang/supertokens"
)
func main() {
err := supertokens.Init(supertokens.TypeInput{
Supertokens: &supertokens.ConnectionInfo{
ConnectionURI: "...",
APIKey: "...",
},
AppInfo: supertokens.AppInfo{
AppName: "...",
APIDomain: "...",
GetOrigin: func(request *http.Request, userContext supertokens.UserContext) (string, error) {
if request != nil {
origin := request.Header.Get("origin")
if origin == "" {
// this means the client is in an iframe, it's a mobile app, or
// there is a privacy setting on the frontend which doesn't send
// the origin
} else {
if origin == "https://test.example.com" {
// query from the test site
return "https://test.example.com", nil
} else if origin == "http://localhost:3000" {
// query from local development
return "http://localhost:3000", nil
}
}
}
// in case the origin is unknown or not set, we return a default
// value which will be used for this request.
return "https://test.example.com", nil
},
},
RecipeList: []supertokens.Recipe{ /*...*/ },
})
if err != nil {
panic(err.Error())
}
}
from supertokens_python import init, InputAppInfo, SupertokensConfig
from supertokens_python.framework.request import BaseRequest
from typing import Optional, Dict, Any
def get_origin(request: Optional[BaseRequest], user_context: Dict[str, Any]) -> str:
if request is not None:
origin = request.get_header("origin")
if origin is None:
# this means the client is in an iframe, it's a mobile app, or
# there is a privacy setting on the frontend which doesn't send
# the origin
pass
else:
if origin == "https://test.example.com":
# query from the test site
return "https://test.example.com"
elif origin == "http://localhost:3000":
# query from local development
return "http://localhost:3000"
# in case the origin is unknown or not set, we return a default
# value which will be used for this request.
return "https://test.example.com"
init(
app_info=InputAppInfo(app_name="...", api_domain="...", origin=get_origin),
supertokens_config=SupertokensConfig(connection_uri="...", api_key="..."),
framework="fastapi", # works with other frameworks as well.
recipe_list=[
# ...
],
)
We remove the
websiteDomain
config in theappInfo
config, and replace it with theorigin
config (GetOrigin
for Golang) as shown above.The
origin
config is a function that takes an optional request object, and returns a string representing the domain of the request. We first check for if therequest
is not provided, and if not, then we fallback on a default value. If it is provided, then we attempt to read theorigin
header from the request and return a domain based on that.There may be cases where the
origin
header is not set, in which case we fallback on the default value once again. This can happen for:- Mobile app frontends.
- Frontend in an iframe
- Privacy setting on the frontend. In these cases, you may want to set a custom prop in all requests (in the URL as a query param) indicating the type of client, read that from the request object above and return a domain based on that.
There are several cases where the input request object may not be provided:
In case you are calling a function like
EmailVerification.createEmailVerificationLink
manually from your APIs.In case of certain dashboard actions like sending an email verification link - here the request object is omited cause the origin will be that of the dashboard and not your frontend app.
Therefore, in these cases, it makes sense to fallback on a sensible default.
caution
Do not return the origin
header as is because it can result in an attack wherein an attacker can inject their custom origin header value in the request, and the links generated (for example for password reset) would then point to their custom origin.
Instead, as shown above, check for each known origin explicitly (including port and protocol), and return only known values.
#
Step 2: Determine the right method for sending session tokens (cookies vs auth header)important
This section is only applicable for browser based frontend config.
By default, browser based apps use cookies for session tokens which are saved against the API domain. Keeping this in mind, you need to think through the right setting for token transfer method based on the following scenarios:
If users can login to multiple frontends at the same time:
- Via login forms on each of those frontends:
- For example, you have two websites:
a.example.com
andb.example.com
(orapp1.com
andapp2.com
), and both have their respective login forms on which users can login. Both of these query the sameapiDomain
(for example,api.example.com
). In this case, you should use header based auth and not cookie based. If you use cookie based auth, then logging into the second site will overwrite the tokens from the first site since the session tokens are saved against the api domain (which is the same for both). Using header based auth, the tokens will be saved against the frontend domains which are both different, allowing users to be logged into both the sites at the same time.
- For example, you have two websites:
- Via a common login form:
- For example, you have
auth.example.com
which logs users intoa.example.com
andb.example.com
. In this case, you can use cookie based sessions, with the sub domain sharing feature enabled. - If the domains are not sub domains and are entirely different base domains (like
app1.com
andapp2.com
), then you need to use SuperTokens as an OAuth provider. This feature is coming soon.
- For example, you have
- Via login forms on each of those frontends:
If users should not login to multiple frontends at the same time:
- For example, you have
test.example.com
for your staging site andlocalhost:3000
for your local dev site. Both use the sameapiDomain
ofapi.example.com
. In this case, you don't really care what happens if a user logs into both of these sites together. In this case, the login from the second site will overwrite the one from the first site, and that's OK. So you can continue to use cookie based auth.
- For example, you have
If your frontend and api are on different base domains:
- If your frontend is on
app1.com
and yourapiDomain
is onapi.example.com
, they are said to be on different base domains. Another example is the frontend being onlocalhost
and theapiDomain
being onapi.example.com
. - In this case, using cookie based auth will not work on browsers like Safari, or chrome incognito (since by default, they disallow third party cookies). So you should use header based auth instead. SuperTokens allows you to choose header based auth for some sites, and cookies for others.
- Note that if the two domains are sub domains, or the same domain but with differnet ports, then they are considered to be on the same base domain and won't have any issues with cookie based auth.
- If your frontend is on