| Title | Director | Release Year | Genre | Rating |
|---|---|---|---|---|
| {movie.title} | {movie.director} | {movie.release_year} | {movie.genre} | {movie.rating} |
| Title | Director | Release Year | Genre | Rating |
|---|---|---|---|---|
| {movie.title} | {movie.director} | {movie.release_year} | {movie.genre} | {movie.rating} |
| Title | Director | Release Year | Genre | Rating |
|---|---|---|---|---|
| {movie.title} | {movie.director} | {movie.release_year} | {movie.genre} | {movie.rating} |
| Title | Director | Release Year | Genre | Rating |
|---|---|---|---|---|
| {{ movie.title }} | {{ movie.director }} | {{ movie.release_year }} | {{ movie.genre }} | {{ movie.rating }} |
49
{JSON.stringify(session, null, 2)}
50
51
Hello, {session.user?.displayName || session.user?.email}!
16You are not signed in.
2020 Don't have an account? Sign Up 21
2225 We've sent a verification link to {email} 26
2728 Please check your email and click the verification link to activate 29 your account. 30
3133 Back to Sign In 34
3546 Already have an account? Sign In 47
48Verification failed
20{message}
21 22 {urlParams.length > 0 && ( 23URL Parameters:
25 {urlParams.map(([key, value]) => ( 26216 Create your first todo to get started! 217
218{todo.details}
323Your download has started. You can close this window.
140 141 `; 142 } 143 } 144 } catch (err) { 145 const message = (err as Error).message || "An unknown error occurred"; 146 setError(`Failed to view file: ${message}`); 147 console.error("Error viewing file:", err); 148 } finally { 149 setViewingFile(null); 150 } 151 }; 152 153 const handleDeleteFile = async (fileId: string): Promise300 Upload your first file to get started! 301
302| Name | 309Type | 310Size | 311Actions | 312
|---|---|---|---|
| {file.name} | 318{file.mimeType} | 319320 {formatFileSize(file.size || 0)} 321 | 322
323
324
339
348
349
|
350
44
{JSON.stringify(session, null, 2)}
45
46
Hello, {user?.displayName || user?.email}!
15You are not signed in.
1992 Don't have an account? Sign Up 93
9470 We've sent a verification link to {email} 71
7273 Please check your email and click the verification link to activate 74 your account. 75
7678 Back to Sign In 79
80140 Already have an account? Sign In 141
142Verifying your email...
95 96 102✓ Successfully verified!
108You'll be redirected to your profile page shortly...
109Verification failed
115{error}
116 117 {Object.keys(urlParams).length > 0 && ( 118URL Parameters:
120 {Object.entries(urlParams).map(([key, value]) => ( 121Please sign in to view your todos.
249352 Create your first todo to get started! 353
354{todo.details}
459Your download has started. You can close this window.
179 180 `; 181 } 182 } 183 } catch (err) { 184 const message = (err as Error).message || "An unknown error occurred"; 185 setError(`Failed to view file: ${message}`); 186 console.error("Error viewing file:", err); 187 } finally { 188 setViewingFile(null); 189 } 190 }; 191 192 const handleDeleteFile = async (fileId: string): Promise345 Upload your first file to get started! 346
347| Name | 354Type | 355Size | 356Actions | 357
|---|---|---|---|
| {file.name} | 363{file.mimeType} | 364365 {formatFileSize(file.size || 0)} 366 | 367
368
369
384
393
394
|
395
{JSON.stringify($auth.session, null, 2)}
54
You must be signed in to view this page.
62Hello, {$auth.user?.displayName || $auth.user?.email}!
13You are not signed in.
1789 Don't have an account? Sign Up 90
9171 Back to Sign In 72
73131 Already have an account? Sign In 132
133Verifying your email...
88 8995 ✓ Successfully verified! 96
97You'll be redirected to your profile page shortly...
98104 Verification failed 105
106{error}
107 108 {#if Object.keys(urlParams).length > 0} 109111 URL Parameters: 112
113 {#each Object.entries(urlParams) as [key, value] (key)} 114347 Create your first todo to get started! 348
349{todo.details}
435Upload your first file to get started!
350| Name | 357Type | 358Size | 359Actions | 360
|---|---|---|---|
| {file.name} | 366{file.mimeType} | 367{formatFileSize(file.size || 0)} | 368
369
370
384
393
394
|
395
{{ JSON.stringify(session, null, 2) }}
42
Hello, {{ user?.displayName || user?.email }}!
9You are not signed in.
12
43
Don't have an account?
69
Already have an account?
Verifying your email...
8 913 ✓ Successfully verified! 14
15You'll be redirected to your profile page shortly...
1620 Verification failed 21
22{{ error }}
23 2426 URL Parameters: 27
2891 Create your first todo to get started! 92
93{{ todo.details }}
181Upload your first file to get started!
109| Name | 115Type | 116Size | 117Actions | 118
|---|---|---|---|
| {{ file.name }} | 123{{ file.mimeType }} | 124{{ formatFileSize(file.size || 0) }} | 125
126
127
143
152
153
|
154
Hi, ${displayName}! Please click the link to verify your email:
``` # Postmark Integration [Section titled “Postmark Integration”](#postmark-integration) Alternatively, you can use postmark’s native integration and leverage postmark’s templates. To do so, you need to: 1. Configure SMTP settings like below: ```toml [provider.smtp] host = 'postmark' password = '{{ secrets.POSTMARK_API_KEY }}' sender = 'myapp@mydomain.com' ``` Caution It is very important that the `host` is set exactly to `postmark`. 2. In postmark you need to create three templates for each locale with the following alias: * $locale.email-confirm-change * $locale.email-verify * $locale.password-reset For instance: * en.email-confirm-change * en.email-verify * en.password-reset * se.email-confirm-change * se.email-verify * se.password-reset Additionally, if you want to use the [passwordless sign-in](/products/auth/sign-in-magic-link) or OTP sign-in emails, you need to create the following templates as well: * $locale.signin-passwordless * $locale.signin-otp * $locale.elevate-otp After these two steps have been completed, the Auth service will leverage these templates instead of the ones described in the previous section. Note Postmark templates should have access to the same variables as described [previously](#template-variables) # Gravatar > Fetch user profiles automatically from gravatar ## Gravatar [Section titled “Gravatar”](#gravatar) If Gravatar is enabled, Nhost Auth will use the user’s email address to fetch their Gravatar profile picture. If the user doesn’t have a Gravatar profile picture, a default image will be used. There are two options for Gravatars. ### Default Image [Section titled “Default Image”](#default-image) If the user doesn’t have a Gravatar profile picture, a default image will be used. You can choose between the following options: * `404`: Do not load any image if none is associated with the email hash, instead return an HTTP 404 (File Not Found) response. * `mp`: (mystery-person) a simple, cartoon-style silhouetted outline of a person (does not vary by email hash). * `identicon`: a geometric pattern based on an email hash. * `monsterid`: a generated ‘monster’ with different colors, faces, etc. * `wavatar`: generated faces with differing features and backgrounds. * `retro`: awesome generated, 8-bit arcade-style pixelated faces. * `robohash`: a generated robot with different colors, faces, etc. * `blank`: a transparent PNG image. ### Rating [Section titled “Rating”](#rating) Gravatar images are rated by default. You can choose between the following options: * `g`: suitable for display on all websites with any audience type. * `pg`: may contain rude gestures, provocatively dressed individuals, lesser swear words or mild violence. * `r`: may contain such things as harsh profanity, intense violence, nudity, or hard drug use. * `x`: may contain hardcore sexual imagery or extremely disturbing violence. # JSON Web Tokens (JWTs) > Configure how Nhost Auth signs JWT access tokens using symmetric keys, asymmetric keys, or an external signing service. ## Introduction [Section titled “Introduction”](#introduction) JSON Web Tokens (JWT) are encoded strings designed to securely transmit information between parties in the form of a JSON object. Each JWT consists of three parts: * header * payload * signature JWTs are commonly used for authentication post-login. The server generates a token containing user claims (like identity and permissions) that subsequent requests can include to prove authorization. Here’s how JWTs typically work in an authentication flow: 1. User logs in with credentials (username/password) 2. Server validates credentials and generates a signed JWT containing user information and permissions 3. Server sends the JWT to the client, which stores it (usually in browser storage) 4. For subsequent requests, the client includes the JWT in the Authorization header 5. Server verifies the token’s signature and grants access based on the encoded permissions The main advantage is that the server doesn’t need to store session information - all necessary data is contained within the token itself, making it ideal for stateless authentication. Note For more information about JSON Web Tokens, visit [jwt.io](https://jwt.io). ## JWT Configuration [Section titled “JWT Configuration”](#jwt-configuration) You can configure your project to use three different kinds of JWTs: * JWTs signed with symmetric keys * JWTs signed with asymmetric keys * JWTs signed externally via a third-party service Note Currently we default to using symmetric keys for signing JWTs. However, we plan to change this to use asymmetric keys in the near future. ### Symmetric Keys [Section titled “Symmetric Keys”](#symmetric-keys) With symmetric keys, your project uses a single key for both signing and verifying JWTs. This key is stored in the project’s configuration and is responsible for signing JWTs. When a client sends a JWT to the server, the server uses the same key to verify the JWT’s signature. If you need to verify JWTs in a different service, the same key can be used for verification. Since the same key is used for both signing and verification, it is crucial to keep it secret, as sharing it with others can compromise the security of your JWTs. Below you can see an example of a symmetric key configuration: * nhost.toml ```toml [[hasura.jwtSecrets]] type = 'HS256' key = 'f03d5f5a0ed055e3fcbc0a3639405aca0511e6abe6d60e40d1fff610c6248f2a' ``` * dashboard  Note We recommend using a [secret](/platform/cloud/secrets) to configure the key. In addition to `HS256`, you can also use `HS384` and `HS512` for extra security. To quickly generate a key, you can use the following command: * HS256 ```shell openssl rand -base64 32 ``` * HS384 ```shell openssl rand -base64 48 ``` * HS512 ```shell openssl rand -base64 64 ``` ### Asymmetric Keys [Section titled “Asymmetric Keys”](#asymmetric-keys) With asymmetric keys, your project uses a pair of public and private keys for signing and verifying JWTs. The private key, stored securely in the project’s configuration, is used to sign the JWTs. The public key, on the other hand, is made available to clients and is used to verify the JWTs. When a client sends a JWT to the server, the server uses the public key to validate the JWT’s signature. If verification is needed in a different service, the public key can be used without compromising security. Since the public key is only used for verification and the private key for signing, sharing the public key is safe and does not jeopardize the security of your JWTs. Below you can see an example of an asymmetric key configuration: * nhost.toml ```toml [[hasura.jwtSecrets]] type = "RS256" kid = "bskhwtelkajsd" key = "" -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqSFS8Kx9LuiYpIms+NoZ (omitted for brevity) jwIDAQAB -----END PUBLIC KEY----- "" signingKey = "" -----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCpIVLwrH0u6Jik (omitted for brevity) s6fJmz3ZeArPI8KFSI3Q2xqm -----END PRIVATE KEY----- "" ``` * dashboard  In addition to `RS256`, you can also use `RS384` and `RS512` for extra security. To quickly generate a key pair, you can use the following commands: * RS256 ```shell # Generate a private key openssl genpkey -algorithm RSA -out jwt_private.pem -pkeyopt rsa_keygen_bits:2048 # Generate a public key from the private key openssl rsa -pubout -in jwt_private.pem -out jwt_public.pem ``` * RS384 ```shell # Generate a private key openssl genpkey -algorithm RSA -out jwt_private.pem -pkeyopt rsa_keygen_bits:3072 # Generate a public key from the private key openssl rsa -pubout -in jwt_private.pem -out jwt_public.pem ``` * RS512 ```shell # Generate a private key openssl genpkey -algorithm RSA -out jwt_private.pem -pkeyopt rsa_keygen_bits:4096 # Generate a public key from the private key openssl rsa -pubout -in jwt_private.pem -out jwt_public.pem ``` You can then copy the contents of `jwt_private.pem` into the `signingKey` field and the contents of `jwt_public.pem` into the `key` field. The `kid` value in your configuration can be any unique string of your choice and must be distinct for each key. It is used to identify the correct key when verifying JWTs through the JWKS endpoint. ### External Signing [Section titled “External Signing”](#external-signing) If you are using a third party service like Auth0 or Clerk you can configure your project to use their JWK endpoint to verify JWTs. Below you can see an example of an external signing configuration: * nhost.toml ```toml [[hasura.jwtSecrets]] jwk_url = "https://mythirdpartyservice.com/jwks.json" ``` Alternatively, you can configure the public key directly: ```toml [[hasura.jwtSecrets]] type = "RS256" key = "" -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqSFS8Kx9LuiYpIms+NoZ (omitted for brevity) jwIDAQAB -----END PUBLIC KEY----- "" ``` * dashboard  Note When using external signing the Auth service will be automatically disabled. ### Claims Mapping and Namespace [Section titled “Claims Mapping and Namespace”](#claims-mapping-and-namespace) When verifying tokens — especially those issued by an external provider — you can remap their claims onto the session variables your permissions expect, and control where those claims are read from. The `claims_namespace` defaults to `https://hasura.io/jwt/claims` (a [retained identifier](/products/graphql/configuration#naming-conventions) kept for compatibility) and can be overridden with `claims_namespace` or `claims_namespace_path`. Use `claims_map` to project individual claims from a literal value or a JSON path within the token. * nhost.toml ```toml [[hasura.jwtSecrets]] jwk_url = "https://my-auth-server.com/.well-known/jwks.json" issuer = "https://my-auth-server.com" [[hasura.jwtSecrets.claims_map]] claim = "x-some-claim" value = "some-value" [[hasura.jwtSecrets.claims_map]] claim = "x-other-claim" path = "$.user.claim.id" default = "default-value" ``` * Schema ```cue #JWTSecret: ({ type: "HS384" | "HS512" | *"HS256" key: string } | { type: "RS256" | "RS384" | "RS512" key: string signingKey?: string kid?: string } | { jwk_url: #Url | *null }) & { claims_format?: "stringified_json" | *"json" audience?: string issuer?: string allowed_skew?: uint32 header?: string } & { claims_map?: [...#ClaimMap] } & ({ claims_namespace: string | *"https://hasura.io/jwt/claims" } | { claims_namespace_path: string } | *{}) #ClaimMap: { claim: string { value: string } | { path: string default?: string } } ``` The complete `#JWTSecret` schema, with every field and default, is documented in the [configuration reference](/reference/configuration#jwtsecret). ## Verify a JWT [Section titled “Verify a JWT”](#verify-a-jwt) To verify JWTs inside Nhost Functions (for both symmetric and asymmetric keys), see the [JWT Verification guide](/products/functions/guides/jwt-verification). ## Custom Claims [Section titled “Custom Claims”](#custom-claims) You can attach extra information to your JWTs in the form of custom claims. These claims can be used for authorization purposes in your application. For more details on how to add custom claims to your JWTs and how to use them, see the [Permissions Variables](/products/graphql/permissions/permission-variables) documentation. # Multi-Factor Authentication (MFA) > Learn how to add TOTP-based multi-factor authentication to your application for enhanced security. Multi-Factor Authentication adds an extra layer of security by requiring a verification code from an authenticator app (e.g., [Authy](https://authy.com/), [Google Authenticator](https://en.wikipedia.org/wiki/Google_Authenticator)) in addition to the user’s email and password. ## Configuration [Section titled “Configuration”](#configuration) To enable MFA, navigate to **Auth -> Settings -> Authentication** and enable **Multi-Factor Authentication**. You can also configure it using the `nhost.toml` file: nhost.toml ```toml [auth.totp] enabled = true ``` ## Enabling MFA [Section titled “Enabling MFA”](#enabling-mfa) Once MFA is enabled for your project, individual users can activate it on their account. This is a two-step process: ### Step 1: Generate TOTP Secret [Section titled “Step 1: Generate TOTP Secret”](#step-1-generate-totp-secret) Request a TOTP secret and QR code for the user to scan with their authenticator app: ```js const response = await nhost.auth.changeUserMfa(); // Display the QR code image for the user to scan const imageUrl = response.body.imageUrl; // Or show the totpSecret for manual entry const totpSecret = response.body.totpSecret; ``` ### Step 2: Verify and Activate [Section titled “Step 2: Verify and Activate”](#step-2-verify-and-activate) After the user scans the QR code, they enter the 6-digit verification code from their authenticator app to confirm activation: ```js await nhost.auth.verifyChangeUserMfa({ activeMfaType: 'totp', code: '123456', // 6-digit code from authenticator app }); ``` ### Activation Flow [Section titled “Activation Flow”](#activation-flow) ```mermaid sequenceDiagram autonumber actor U as User participant C as Client participant A as Nhost Auth participant G as Authenticator App U-->A: Authenticated C->>+A: GET /mfa/totp/generate A->>-C: QR code image URL + TOTP secret C->>U: Display QR code U->>+G: Scan QR code G->>-U: 6-digit TOTP code C->>+A: POST /user/mfa Note right of C: TOTP code + activeMfaType: "totp" A->>A: Verify code and activate MFA A->>-C: OK ``` ## Signing In with MFA [Section titled “Signing In with MFA”](#signing-in-with-mfa) When a user with MFA enabled signs in with email and password, the server returns a `ticket` instead of a session. The client must then prompt for a TOTP code and verify it to complete sign-in. ### Step 1: Initial Sign-In [Section titled “Step 1: Initial Sign-In”](#step-1-initial-sign-in) ```js const response = await nhost.auth.signInEmailPassword({ email: 'joe@example.com', password: 'secret-password', }); if (response.body?.mfa) { // MFA is required — store the ticket and prompt for TOTP const ticket = response.body.mfa.ticket; // Navigate to MFA verification page } ``` ### Step 2: Verify TOTP Code [Section titled “Step 2: Verify TOTP Code”](#step-2-verify-totp-code) ```js const response = await nhost.auth.verifySignInMfaTotp({ ticket: ticket, // from step 1 otp: '123456', // 6-digit code from authenticator app }); // response.body.session contains the access + refresh tokens ``` ### Sign-In with MFA Flow [Section titled “Sign-In with MFA Flow”](#sign-in-with-mfa-flow) ```mermaid sequenceDiagram autonumber actor U as User participant C as Client participant A as Nhost Auth participant G as Authenticator App U->>C: Enter email + password C->>+A: POST /signin/email-password A->>-C: MFA ticket C->>U: Prompt for TOTP code U->>+G: Open authenticator app G->>-U: 6-digit TOTP code U->>C: Enter TOTP code C->>+A: POST /signin/mfa/totp Note right of C: ticket + TOTP code A->>A: Verify TOTP A->>-C: Session (access + refresh tokens) ``` ## Disabling MFA [Section titled “Disabling MFA”](#disabling-mfa) Users can disable MFA by providing a current TOTP code from their authenticator app: ```js await nhost.auth.verifyChangeUserMfa({ activeMfaType: '', // empty string disables MFA code: '123456', // current 6-digit code from authenticator app }); ``` # OAuth2 / OIDC Provider > Use your Nhost project as an OAuth2 and OpenID Connect Identity Provider for third-party applications Nhost Auth can act as an OAuth2 Authorization Server and OpenID Connect Identity Provider. This lets third-party applications — wikis, dashboards, MCP servers, internal tools — authenticate users through your Nhost project using standard OAuth2/OIDC flows. ```mermaid sequenceDiagram participant App as Third-Party App participant Auth as Nhost Auth participant Consent as Your Consent Page App->>Auth: Redirect to /oauth2/authorize Auth->>Consent: Redirect with request_id Consent->>Consent: User authenticates & approves Consent->>Auth: POST /oauth2/login Auth->>App: Redirect with authorization code App->>Auth: Exchange code for tokens Auth->>App: Access token, ID token, refresh token ``` ## What It Supports [Section titled “What It Supports”](#what-it-supports) * **Three client types** — confidential (with secret), public (no secret, PKCE required), and URL-based (CIMD) * **Scopes** — `openid`, `profile`, `email`, `phone`, `offline_access`, `graphql` * **RSA-signed JWT tokens** — access tokens and ID tokens signed with RSA (RS256, RS384, or RS512), with a JWKS endpoint for verification ## Standards Compliance [Section titled “Standards Compliance”](#standards-compliance) | Standard | Description | | -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | | [RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749) | OAuth 2.0 Authorization Framework — Authorization Code grant | | [RFC 6750](https://datatracker.ietf.org/doc/html/rfc6750) | OAuth 2.0 Bearer Token Usage | | [RFC 7009](https://datatracker.ietf.org/doc/html/rfc7009) | Token Revocation — Refresh token revocation | | [RFC 7517](https://datatracker.ietf.org/doc/html/rfc7517) | JSON Web Key (JWK) — JWKS endpoint for public signing keys | | [RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519) | JSON Web Token (JWT) — RSA-signed access and ID tokens | | [RFC 7636](https://datatracker.ietf.org/doc/html/rfc7636) | PKCE — Proof Key for Code Exchange (S256 only) | | [RFC 7662](https://datatracker.ietf.org/doc/html/rfc7662) | Token Introspection — Check token validity | | [RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414) | Authorization Server Metadata — Discovery endpoints | | [RFC 9207](https://datatracker.ietf.org/doc/html/rfc9207) | Issuer Identification — `iss` parameter in authorization responses | | [RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728) | Client ID Metadata Document — URL-based client IDs for MCP and similar tools | | [OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html) | ID tokens, UserInfo, Discovery, standard claims | ## Prerequisites [Section titled “Prerequisites”](#prerequisites) * An **[RSA signing key](/products/auth/jwt#asymmetric-keys)** must be configured. Other algorithms are not supported when the OAuth2 provider is enabled. * The OAuth2 provider must be **explicitly enabled** via configuration. ## Configuration [Section titled “Configuration”](#configuration) * Dashboard Go to your project’s dashboard, then **Auth** → **Settings** → **OAuth2 Provider**.  * nhost.toml ```toml [auth.oauth2Provider] # Enable the OAuth2/OIDC provider enabled = true # default: false # URL where users are redirected for login and consent # You need to implement this page yourself, check our guides for details loginURL = 'https://example.com/oauth2/login' [auth.oauth2Provider.accessToken] # Access token lifetime in seconds expiresIn = 900 # default: 900 - 15 minutes [auth.oauth2Provider.refreshToken] # Refresh token lifetime in seconds expiresIn = 2592000 # default: 2592000 - 30 days [auth.oauth2Provider.clientIdMetadataDocument] # Enable URL-based client IDs per RFC 9728, used by MCP and similar tools enabled = true # default: false ``` ## Next Steps [Section titled “Next Steps”](#next-steps) ### [Managing Clients](/products/auth/oauth2-provider/managing-clients) [Create and manage OAuth2 clients via GraphQL](/products/auth/oauth2-provider/managing-clients) ### [Authorization Code Flow](/products/auth/oauth2-provider/authorization-flow) [Understand the flow and build your consent page](/products/auth/oauth2-provider/authorization-flow) ### [Tokens and Scopes](/products/auth/oauth2-provider/tokens-and-scopes) [Understand token formats, scopes, and claims](/products/auth/oauth2-provider/tokens-and-scopes) # Authorization Code Flow > Understand the OAuth2 authorization code flow and build your consent page When a third-party app wants to authenticate one of your users, it triggers the Authorization Code flow. This is a standard OAuth2 flow where three parties are involved: | Party | Who | Role | | --------------------- | ----------------------------------------------- | -------------------------------------------------- | | **Third-Party App** | The external service (e.g. a wiki, an MCP tool) | Initiates the flow and receives tokens | | **Nhost Auth** | Your Nhost project’s auth service | Validates requests, issues codes and tokens | | **Your Consent Page** | A page **you build** in your frontend | Authenticates the user and collects their approval | ```mermaid sequenceDiagram participant App as Third-Party App participant Auth as Nhost Auth participant Consent as Your Consent Page App->>Auth: Redirect to /oauth2/authorize Auth->>Auth: Validate client, redirect_uri, scopes Auth->>Consent: Redirect with request_id Consent->>Auth: GET /oauth2/login?request_id=