diff --git a/src/content/docs/authenticate/custom-configurations/redirect-users.mdx b/src/content/docs/authenticate/custom-configurations/redirect-users.mdx index 0ea52980e..77e529e5f 100644 --- a/src/content/docs/authenticate/custom-configurations/redirect-users.mdx +++ b/src/content/docs/authenticate/custom-configurations/redirect-users.mdx @@ -105,7 +105,7 @@ Because it is just a string, you can leverage it to store additional information ``` 5. As part of your callback processing and response validation, verify that the `state` returned in the URL above matches the random string you stored locally. If it does, retrieve the rest of the application state (like the nextUrl). -6. Use the `code` param to complete the token exchange (as per the [Use Kinde without an SDK](/developer-tools/about/using-kinde-without-an-sdk/#handling-the-callback) guide) and once the exchange is complete use the `nextUrl` to redirect the user. +6. Use the `code` param to complete the token exchange (as per the [Use Kinde without an SDK](/developer-tools/about/using-kinde-without-an-sdk/) guide) and once the exchange is complete use the `nextUrl` to redirect the user. ## **Limitations and considerations** diff --git a/src/content/docs/developer-tools/about/using-kinde-without-an-sdk.mdx b/src/content/docs/developer-tools/about/using-kinde-without-an-sdk.mdx index e0c25ca4c..201187de3 100644 --- a/src/content/docs/developer-tools/about/using-kinde-without-an-sdk.mdx +++ b/src/content/docs/developer-tools/about/using-kinde-without-an-sdk.mdx @@ -1,9 +1,11 @@ --- page_id: 02d02820-92da-4721-9a91-222c9b095869 -title: Using Kinde without an SDK +title: Use Kinde without an SDK description: "Comprehensive guide for integrating with Kinde without SDKs using OAuth 2.0 and OpenID Connect standards including authorization flows, token exchange, and request parameters." sidebar: order: 2 +tableOfContents: + maxHeadingLevel: 3 relatedArticles: - 684fc526-a338-4a67-9af6-742a39b66aff - 5a248c6f-c1ae-480a-95c3-d3c69c81598d @@ -16,6 +18,9 @@ topics: - developer-tools - oauth - openid-connect + - authentication + - tokens + - security sdk: [] languages: - javascript @@ -25,126 +30,457 @@ complexity: advanced keywords: - OAuth 2.0 - OpenID Connect - - callback URLs - - authorization code + - Authorization Code Flow - PKCE - - access tokens + - Proof Key for Code Exchange + - access token + - id_token + - refresh token + - token exchange + - JWKS + - JWT verification - scopes - - request parameters -updated: 2026-03-19 + - openid + - offline + - callback URL + - redirect_uri + - state parameter + - CSRF protection + - nonce + - prompt + - login_hint + - org_code + - multi-tenant + - userinfo endpoint + - route protection + - sign in + - sign up + - logout + - custom domain + - no SDK + - framework agnostic +updated: 2026-05-23 featured: false deprecated: false -ai_summary: Comprehensive guide for integrating with Kinde without SDKs using OAuth 2.0 and OpenID Connect standards including authorization flows, token exchange, and request parameters. +ai_summary: "This guide explains how to integrate Kinde authentication into any language or framework without using an official SDK, relying entirely on OAuth 2.0 and OpenID Connect standards. A 9-step quickstart covers both the Authorization Code Flow for server-rendered back-end applications and the Authorization Code Flow with PKCE for single-page apps and mobile clients — including generating a PKCE code verifier and challenge, building the authorization URL, handling the callback (with state validation to prevent CSRF attacks), exchanging the authorization code for tokens, and decoding the id_token to display user information. The guide details how to protect routes at two layers: a frontend token expiry check and backend RS256 JWT verification against the JWKS endpoint. It also covers silently refreshing access tokens with a refresh token and signing users out via the Kinde logout endpoint. A full reference covers all 24 supported request parameters — including prompt, login_hint, org_code, nonce, and audience — ordered by importance. FAQs address why the Implicit Flow is not supported and when to use the userinfo endpoint instead of decoding the id_token directly." --- Kinde is designed to help founders and developers build SaaS products by providing software infrastructure like authentication, feature flags, user management, and more. -We support connecting to Kinde through [our SDKs](/developer-tools/about/our-sdks/), but everything we build is also OAuth 2 standard, so you can integrate into any language framework with Kinde without an SDK. +We support connecting to Kinde through [our SDKs](/developer-tools/about/our-sdks/), but everything we build follows the OAuth 2.0 standard, so you can integrate Kinde into any language or framework without an SDK. -## **Get started** +## What you need -[Start for free](https://kinde.com/) on Kinde. +- A [Kinde](/get-started/guides/first-things-first/) account with **Admin** or **Engineer** access (Sign up for free) +- Knowledge of programming languages and OAuth 2.0 -## **OpenID Connect** +## Quickstart -To connect to Kinde you need to know where the endpoints are for things like authorization, tokens, and user profiles. You’ll also need to know the response types, scopes, and claims that are supported. +### 1. Create a Kinde application -All this data and more can be found in your OpenID configuration file which is located at: +1. Sign in to your Kinde dashboard and select **Add application** +2. Enter a name for the application (e.g., “My App”) +3. Select an application type: + - **Back-end web**: for server-rendered or full-stack apps + - **Front-end web**: for JavaScript-based single-page applications (SPAs) or mobile apps -`https://.kinde.com/.well-known/openid-configuration` +4. Select **Other** +5. Select **Save**. -## **Set** **callback URLs in Kinde** +### 2. Get your app keys -During the auth process your user will authenticate on Kinde and once authenticated will be redirected back to an endpoint in your code. To define this callback url: +1. In your app settings page, select **Details** +2. Copy your app keys: -1. In Kinde, go to **Settings > Applications > [Your app] > View details**. -2. Add your callback URLs in the relevant fields. For example: - - Allowed callback URLs (also known as redirect URIs) - for example [`http://localhost:3000/api/auth/kinde_callback`](http://localhost:3000/api/auth/kinde_callback) - - Allowed logout redirect URLs - for example `http://localhost:3000` -3. Select **Save**. + - **Custom domain**: if you have configured a [custom domain](/build/domains/pointing-your-domain/) + - **Domain**: your Kinde domain (use it if you don't have a custom domain) + - **Client ID**: a unique identifier for your app + - **Client secret**: (only for back-end apps) + + You will need these keys to connect your codebase with Kinde. -You will need to use this redirect URL in the following step. +### 3. Add a callback URL -## **Signing up and signing in** +1. In your app's **Details** page, scroll down to the **Callback URLs** section +2. Add the following URLs: + - Allowed callback URLs (e.g., `http://localhost:3000/auth/callback`) + - Allowed logout redirect URLs (e.g., `http://localhost:3000`) -Your users must be redirected from your product to Kinde to sign up or sign in securely. The redirect URL on your product side would look like the following: + You can add more than one callback URL per line. -```markdown -https://.kinde.com/oauth2/auth -?response_type=code -&client_id= -&redirect_uri= -&scope=openid%20profile%20email -&state=abc -``` + +3. Select **Save** + +### 4. Set authentication methods + +1. In your app's settings page, go to **Authentication** +2. Enable the authentication methods you want your users to sign in with (e.g., Google, Apple, SSO, etc.) +3. Select **Save** + +### 5. Get the OpenID endpoints + +These are the OpenID endpoints for Kinde, found at: + +`https:///.well-known/openid-configuration` + + + +- Authorization endpoint: `https:///oauth2/auth` +- Token endpoint: `https:///oauth2/token` +- Userinfo endpoint: `https:///oauth2/v2/user_profile` +- Logout endpoint: `https:///logout` + +**Other endpoints:** + +- JWKS URI: `https:///.well-known/jwks` +- Revocation endpoint: `https:///oauth2/revoke` +- Introspection endpoint: `https:///oauth2/introspect` -Note: Never include the client secret in the URL as this is publicly viewable. +### 6. Create the authorization request -Kinde supports all the standard OAuth 2 request parameters as well as a few additional Kinde-specific parameters to improve the end user experience. Full details can be found in the **Request parameters** table below. +1. Your user clicks a button to sign in or sign up in your app. -Kinde also supports the PKCE extension, in which case the `code_challenge` and `code_challenge_method` parameters are also required. This is recommended for mobile apps and single page applications (SPAs). -## Handling successful auth for desktop and mobile apps + + + 2. In your app, generate a random `state` value: -If you offer mobile and desktop apps, as well as access through a browser, you'll need to handle the post-authentication browser state. Rather than leaving a hanging screen, you can show users a success page. To do this, add the `is_use_auth_success_page` parameter to the authorization URL. See the [Request prameters](/developer-tools/about/using-kinde-without-an-sdk/#request-parameters) section below. + ```js title="JavaScript example" + const state = crypto.randomBytes(32).toString('hex'); + ``` + + Store `state` in your server-side session — you will need it when the user returns to the callback URL. + + 3. Send the user to Kinde's authorization endpoint with the state parameter included in the URL: + + ```http + GET https:///oauth2/auth + ?response_type=code + &client_id= + &redirect_uri= + &scope=openid%20profile%20email + &state= + ``` + + + 2. In your app, generate a random `state` value, a `code_verifier`, and derive a `code_challenge` from it: + + ```js title="JavaScript example" + const state = crypto.randomBytes(32).toString('hex'); + + // 64 random bytes, base64url-encoded = 86-char code_verifier + const codeVerifier = crypto.randomBytes(64).toString('base64url'); + + // SHA-256 hash of the verifier, base64url-encoded = code_challenge + const codeChallenge = crypto + .createHash('sha256') + .update(codeVerifier) + .digest('base64url'); + ``` + + Store `state` and `code_verifier` in `sessionStorage` — you will need them when the user returns to the callback URL. + + 3. Send the user to Kinde's authorization endpoint with the state and PKCE parameters included in the URL: + + ```http + GET https:///oauth2/auth + ?response_type=code + &client_id= + &redirect_uri= + &scope=openid%20profile%20email + &state= + &code_challenge= + &code_challenge_method=S256 + ``` + + + -## Handling the callback +4. The user is redirected to the Kinde sign in page and authenticates with their credentials (email/password, social login, SSO, etc.). -As mentioned before when a user authenticates through Kinde, we will redirect them to the endpoint you defined in the previous step. As part of this redirect, Kinde provides an authorization `code` as a query parameter in the URL. +### 7. Handle the callback + +1. Kinde redirects the user back to your `redirect_uri` with an authorization `code` and the original `state` value as query parameters: + + ```text + https://your-app.com/auth/callback + ?code= + &state= + ``` + +2. Validate that the returned `state` matches the one you generated in the previous step to prevent CSRF attacks. -Using the localhost example from before the URL would look something like: + + + 3. Exchange the authorization `code` for tokens by making a POST request to the Kinde token endpoint: -```jsx -http://localhost:3000/api/auth/kinde_callback + ```http + POST https:///oauth2/token + Content-Type: application/x-www-form-urlencoded + + grant_type=authorization_code + &code= + &redirect_uri= + &client_id= + &client_secret= + ``` + + + 3. Exchange the authorization `code` for tokens by making a POST request to the Kinde token endpoint with the `code_verifier` — no `client_secret` needed: + + ```http + POST https:///oauth2/token + Content-Type: application/x-www-form-urlencoded + + grant_type=authorization_code + &code= + &redirect_uri= + &client_id= + &code_verifier= + ``` + + + +4. Kinde returns the tokens in the response body (same for both flows): + + ```jsonc + { + "access_token": "...", + "id_token": "...", + "refresh_token": "..." // only if the "offline" scope was requested + } + ``` + +### 8. Display user information + +1. Redirect the user to a protected page in your app +2. Decode the `id_token` with any standard [JWT library](https://www.jwt.io/libraries), or if you are using JavaScript, use the [Kinde JavaScript library](/build/tokens/decode-jwts/) +3. Use the decoded claims to display in your app: + + ```json + { + "sub": "kp_7a4b2c...", + "given_name": "Jane", + "family_name": "Doe", + "email": "jane@example.com", + "picture": "https://gravatar.com/...", + "iat": 1716800000, + "exp": 1716886400 + } + ``` + +Alternatively, you can call the userinfo endpoint, passing the `access_token` as a Bearer token. This always returns an up-to-date version of the user's profile. See [When should I use the userinfo endpoint instead of decoding the `id_token`?](#when-should-i-use-the-userinfo-endpoint-instead-of-decoding-the-id_token) for more details. + +### 9. Test user registration + +Complete the registration flow in your app and verify that you can sign in to a protected page (e.g., a dashboard). + +1. Go to your Kinde dashboard and select **Users** +2. You should see the new user's details in the list. + +## Authentication + +### Create a sign in or sign up link + +The following link redirects the user to the Kinde sign-in page by default: + + ```http + GET https:///oauth2/auth + ?response_type=code + &client_id= + &redirect_uri= + &scope=openid%20profile%20email + &state= + ``` + +To send the user to the sign-up page instead, add `prompt=create` to the URL: + + ```http + GET https:///oauth2/auth + ?response_type=code + &client_id= + &redirect_uri= + &scope=openid%20profile%20email + &state= + &prompt=create + ``` + +### Handle successful auth for desktop and mobile apps + +If you offer mobile and desktop apps alongside a browser-based version, you'll need to handle the post-authentication browser state. Rather than leaving a hanging screen, you can show users a success page. To do this, add the `is_use_auth_success_page` parameter to the authorization URL. See the [Request parameters](/developer-tools/about/using-kinde-without-an-sdk/#request-parameters) section below. + +### Protect a route with auth + +Route protection works at two layers. Both are required — the frontend layer is UX, the backend layer is the real security gate. + +**Layer 1: Frontend — redirect unauthenticated users** + +Before rendering a protected page or screen, check that you have a valid, non-expired access token. If not, redirect to the Kinde authorization endpoint: + +```js +function isTokenExpired(token) { + // JWT payloads are base64url-encoded; convert to standard base64 before decoding + const base64Url = token.split('.')[1]; + const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); + const { exp } = JSON.parse(atob(base64)); + return Date.now() >= exp * 1000; +} + +// Run this check on every protected page load +if (!accessToken || isTokenExpired(accessToken)) { + window.location.href = + 'https:///oauth2/auth?response_type=code&client_id=&...'; +} +``` + + + +**Layer 2: Backend — verify the token on every request** + +Your server must validate the access token on every call to a protected endpoint. Send the token in the `Authorization` header: + +```http +GET /api/protected-resource +Authorization: Bearer +``` + +On the server, verify the token before returning any data: + +```js +// Pseudocode — pattern is the same in Node, Python, Go, etc. +function protectedRoute(req, res, next) { + const token = req.headers.authorization?.replace('Bearer ', ''); + if (!token) return res.status(401).json({ error: 'Unauthorized' }); + + try { + const payload = verifyJWT(token, { + jwksUri: 'https:///.well-known/jwks', + issuer: 'https://', + }); + req.user = payload; // access sub, org_code, scp, permissions + next(); + } catch { + return res.status(401).json({ error: 'Invalid or expired token' }); + } +} ``` -You need to extract this authorization `code` parameter from the URL and exchange it for an access token by making a POST request to the Kinde token endpoint `https://.kinde.com/oauth2/token`. +See [Validate the access token](#validate-the-access-token) below for the full list of claims to check. + +**Using claims for authorization** + +Once the token is verified, use the claims inside it to make access decisions: + +| Claim | Use for | +| --- | --- | +| `sub` | Identify the user — link to your own database records | +| `scp` | Check the user has the required OAuth scopes | +| `permissions` | Fine-grained access control (e.g. `read:reports`) | +| `org_code` | Multi-tenant apps — confirm the user belongs to the right organization | + +**Handling token expiry** + +By default, access tokens expire after 24 hours. If you requested the `offline` scope, use the refresh token to get a new access token silently — without sending the user through the login flow again: + + + + ```http + POST https:///oauth2/token + Content-Type: application/x-www-form-urlencoded + + grant_type=refresh_token + &client_id= + &client_secret= + &refresh_token= + ``` + + + ```http + POST https:///oauth2/token + Content-Type: application/x-www-form-urlencoded + + grant_type=refresh_token + &client_id= + &refresh_token= + ``` + + + +If no refresh token is available (i.e. `offline` scope was not requested), redirect the user to sign in again. -As well as the `code` you have received, this request should also include parameters such as the `client_id`,  `client_secret` \*\*,  `redirect_uri`, and `grant_type` (which should be set to `authorization_code`). +### Validate the access token -Make a POST request with the following payload: +Always verify the `access_token` signature on your server before trusting any request. Kinde signs tokens with RS256 and publishes its public keys at the JWKS endpoint: -```markdown -client_id= -&client_secret= -&grant_type=authorization_code -&redirect_uri= -&code= +```text +https:///.well-known/jwks ``` -Make sure you replace the `` with your own credentials. +Most JWT libraries accept a JWKS URL directly — point your library at this URL and it will fetch and cache the correct public key automatically. -This url can now be used to return the access token which can be stored and later used to make authenticated requests on behalf of the user. +As part of verification, validate the following claims: -Note that the `code_verifier` is also required in PKCE flow. The response body will then contain a token that you can decode. +| Claim | Expected value | +| --- | --- | +| `iss` | `https://` | +| `aud` | Your `client_id` (or your API audience if configured) | +| `exp` | Must be in the future | +| `iat` | Must be in the past | -Do not include the client secret in frontend / single page applications as this is publicly viewable - instead, use the PKCE extension. +Reject any token that fails signature verification or has an unexpected claim value. -## **Supported grant types for getting access tokens** +### Sign out your users -### **Authorization Code Flow** +To sign a user out: -Recommended for regular web applications rendered on the server. +1. Clear any session or token storage in your app +2. Redirect them to the Kinde logout endpoint with your logout URL: -### **Authorization Code Flow with Proof Key for Code Exchange (PKCE)** + ```text + https:///logout?redirect= + ``` -Kinde supports the PKCE extension, in which case the `code_challenge` and `code_challenge_method` parameters are also required. This is recommended for mobile apps and single page applications (SPAs). + This ends their Kinde session. They will need to authenticate again to receive new tokens. -### **Implicit flow (not supported)** +To register a logout URL, go to **Settings > Applications > [Your app] > View details**, then add it to the **Allowed logout redirect URLs** field. -Before PKCE (see above) this was the method used by applications that were unable to store secrets securely. This flow has security implications and Kinde does not support it for this reason. +## Supported grant types + +### Authorization Code Flow + +Recommended for server-rendered web applications. + +### Authorization Code Flow + PKCE + +Kinde supports the Proof Key for Code Exchange (PKCE) extension. When using PKCE, include the `code_challenge` and `code_challenge_method` parameters in the authorization request. This flow is recommended for mobile apps and single-page applications (SPAs). ## OAuth 2.0 scopes The following scopes can be requested from Kinde. -- `openid` - requests an ID token which contains information about the user +### `openid` + +Requests an `id_token` that contains information about the authenticated user. -- `email` - requests a user's email +### `email` -- `profile` - requests profile details as part of ID token, e.g. name, family name, given name, picture (avatar) +Requests the `email` and `email_verified` claims in the `id_token`. -- `offline` - request to act on behalf of the user even if they're offline +### `profile` + +Requests profile details as part of the `id_token`, including given name, family name, and picture. + +### `offline` + +Requests a `refresh_token` that can be used to silently refresh the `access_token` when it expires.