Authentication using JWTs
important
Using SuperTokens with Hasura requires you to host your own API layer that uses our Backend SDK. If you do not want to host your own server you can use a serverless environment (AWS Lambda for example) to achieve this.
#
1) Complete the setup guidesFollow the frontend and backend pre-built UI setup guides to setup SuperTokens.
This involves setting up the frontend SDK, the backend SDK, and the SuperTokens core. With this, you should have login / session refreshing / sign out setup on your website. The next steps will be about how to authenticate API calls to Hasura using the SuperTokens session.
#
2) Expose the access token to the frontendFor cookie based auth, the access token is not available on the frontend by default. In order to expose it, you need to set the exposeAccessTokenToFrontendInCookieBasedAuth
config to true
.
- NodeJS
- GoLang
- Python
- Other Frameworks
Important
import SuperTokens from "supertokens-node";
import Session from "supertokens-node/recipe/session";
SuperTokens.init({
supertokens: {
connectionURI: "..."
},
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
recipeList: [
Session.init({
exposeAccessTokenToFrontendInCookieBasedAuth: true
})
]
});
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{
ExposeAccessTokenToFrontendInCookieBasedAuth: true,
}),
},
})
}
from supertokens_python import init, InputAppInfo
from supertokens_python.recipe import session
init(
app_info=InputAppInfo(
api_domain="...", app_name="...", website_domain="..."),
framework='...',
recipe_list=[
session.init(
expose_access_token_to_frontend_in_cookie_based_auth=True,
)
]
)
#
3) Add custom claims to the JWTimportant
Hasura requires claims to be set in a specific way, read the official documentation to know more.
- NodeJS
- GoLang
- Python
- Other Frameworks
Important
import SuperTokens from "supertokens-node";
import Session from "supertokens-node/recipe/session";
SuperTokens.init({
supertokens: {
connectionURI: "...",
},
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
recipeList: [
Session.init({
exposeAccessTokenToFrontendInCookieBasedAuth: true,
override: {
functions: function (originalImplementation) {
return {
...originalImplementation,
createNewSession: async function (input) {
input.accessTokenPayload = {
...input.accessTokenPayload,
"https://hasura.io/jwt/claims": {
"x-hasura-user-id": input.userId,
"x-hasura-default-role": "user",
"x-hasura-allowed-roles": ["user"],
}
};
return originalImplementation.createNewSession(input);
},
};
}
},
})
]
});
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{
ExposeAccessTokenToFrontendInCookieBasedAuth: true,
Override: &sessmodels.OverrideStruct{
Functions: func(originalImplementation sessmodels.RecipeInterface) sessmodels.RecipeInterface {
originalCreateNewSession := *originalImplementation.CreateNewSession
(*originalImplementation.CreateNewSession) = func(userID string, accessTokenPayload, sessionDataInDatabase map[string]interface{}, disableAntiCsrf *bool, userContext supertokens.UserContext) (sessmodels.SessionContainer, error) {
if accessTokenPayload == nil {
accessTokenPayload = map[string]interface{}{}
}
hasuraClaims := map[string]interface{}{
"x-hasura-user-id": userID,
"x-hasura-default-role": "user",
"x-hasura-allowed-roles": []string{"user"},
}
accessTokenPayload["https://hasura.io/jwt/claims"] = hasuraClaims
return originalCreateNewSession(userID, accessTokenPayload, sessionDataInDatabase, disableAntiCsrf, userContext)
}
return originalImplementation
},
},
}),
},
})
}
from supertokens_python import init, InputAppInfo
from supertokens_python.recipe import session
from supertokens_python.recipe.session.interfaces import RecipeInterface
from typing import Dict, Optional, Any
def override_functions(original_implementation: RecipeInterface):
original_implementation_create_new_session = original_implementation.create_new_session
async def create_new_session(user_id: str,
access_token_payload: Optional[Dict[str, Any]],
session_data_in_database: Optional[Dict[str, Any]],
disable_anti_csrf: Optional[bool],
user_context: Dict[str, Any]):
if access_token_payload is None:
access_token_payload = {}
access_token_payload['https://hasura.io/jwt/claims'] = {
"x-hasura-user-id": user_id,
"x-hasura-default-role": 'user',
"x-hasura-allowed-roles": ['user'],
}
return await original_implementation_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, user_context)
original_implementation.create_new_session = create_new_session
return original_implementation
init(
app_info=InputAppInfo(
api_domain="...", app_name="...", website_domain="..."),
framework='...',
recipe_list=[
session.init(
override=session.InputOverrideConfig(
functions=override_functions
),
expose_access_token_to_frontend_in_cookie_based_auth=True,
)
]
)
#
4) Configure Hasura environment variablesinfo
Read the official documentation to know about setting the JWT secret environment variable on Hasura
To use JWT based authentication, Hasura requires setting environment variables when configuring your app. With SuperTokens this can be done in 2 ways:
#
Using the JWKS endpointWhen configuring Hasura, you can set the jwk_url
property.
{
"jwk_url": "{apiDomain}/{apiBasePath}/jwt/jwks.json"
}
You can get the jwks URL for your backend by using the method explained here
#
Using a key stringHasura let's you provide a PEM string in the configuration. Refer to this page to know how to get a public key as a string, you can then use that key string in the Hasura config:
{
"type": "RS256",
"key": "CERTIFICATE_STRING",
}
#
5) Making requests to Hasura#
a) Getting the JWT on the frontend- Web
- Mobile
- Via NPM
- Via Script Tag
import Session from "supertokens-web-js/recipe/session";
async function getToken(): Promise<void> {
const accessToken = await Session.getAccessToken();
console.log(accessToken);
}
async function getToken(): Promise<void> {
const accessToken = await supertokensSession.getAccessToken();
console.log(accessToken);
}
- React Native
- Android
- iOS
- Flutter
import SuperTokens from 'supertokens-react-native';
async function getToken(): Promise<void> {
const accessToken = await SuperTokens.getAccessToken();
console.log(accessToken);
}
import android.app.Application
import com.supertokens.session.SuperTokens
class MainApplication: Application() {
fun getToken(): String {
return SuperTokens.getAccessToken(applicationContext)
}
}
import UIKit
import SuperTokensIOS
class ViewController: UIViewController {
func getToken() -> String? {
return SuperTokens.getAccessToken()
}
}
import 'package:supertokens_flutter/supertokens.dart';
Future<String?> getToken() async {
return await SuperTokens.getAccessToken();
}
#
b) Making HTTP requestsimport axios from "axios";
async function makeRequest() {
let url = "...";
let jwt = "..."; // Refer to step 5.a
let response = await axios.get(url, {
headers: {
"Authorization": `Bearer ${jwt}`,
},
});
}
#
During Local developmentIf you are using Hasura cloud and testing your backend APIs in your local environment, JWT verification will fail because Hasura will not be able to query the JWKS endpoint (because the cloud can not query your local environment i.e localhost, 127.0.0.1).
To solve this problem you will need to expose your locally hosted backend APIs to the internet. For example you can use ngrok. After that, you need to configure Hasura to use the {ngrokURL}/{apiBasePath}/jwt/jwks.json
as the JWKS endpoint (explained in step 4)