OAuth (Open Authorization) is an open-standard framework for access delegation, allowing applications to securely access a user’s protected resources without exposing their login credentials. By keeping passwords private and limiting access to sensitive information, OAuth improves both security and access management across web, mobile, and desktop applications. OAuth 2.0, the current and most widely adopted version, expands upon the original protocol to support APIs, mobile apps, and connected devices, offering multiple authorization flows tailored to different application types.
Corti Assistant requires user-based authentication. Client credentials flows and other machine-to-machine authentication methods are NOT supported for embedded Corti Assistant integrations. You must authenticate as an end user, not as an application.
When embedding Corti Assistant in your application (via iFrame/WebView), it’s important to use the right OAuth2 grant type that supports user-based authentication.This guide explains the OAuth flows that work with embedded Corti Assistant, focusing on user-based authentication methods that are suitable for interactive scenarios.
Supported OAuth Grant Types for Embedded Corti Assistant
The following OAuth flows support user-based authentication and can be used with embedded Corti Assistant. Client credentials grant is NOT supported as it does not provide user context.
1. Authorization Code Flow with PKCE (Recommended)
Best for: Native apps, single-page apps, or any browser-based integration where a user is present.Why: This flow is secure, interactive, and doesn’t require a client secret (ideal for public clients). Proof Key for Code Exchange (PKCE) protects against code interception attacks.How it works:
Your app redirects the user to Corti’s OAuth2 authorization server.
The user logs in and grants permission.
Corti redirects back with an authorization code.
Your app exchanges the code (with the PKCE verifier) for an access token.
Best for: Server-side web applications embedding Corti Assistant where the client secret can be safely stored on the backend.Why: Similar to PKCE, but requires storing a client secret — which is not safe in public or browser-based clients.Key Concerns:
Unsafe for apps where the frontend or iFrame can be inspected.
Only acceptable in secure backend environments where the client secret can be protected.
Step 2: Handle the Callback and Exchange Code for Tokens
Copy
Ask AI
// Extract the authorization code from URL parametersconst urlParams = new URLSearchParams(window.location.search);const code = urlParams.get('code');const error = urlParams.get('error');if (error) { console.error('Authorization failed:', error); return;}if (code) { // Exchange the authorization code for tokens using SDK (client-side) const response = await fetch('http://localhost:3000/callback', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ code }), }); if (!response.ok) { throw new Error('Authentication failed'); } const tokens = await response.json();}
Step 3: Exchange Code for Access Token (Backend)
Copy
Ask AI
// server.js or routes/callback.jsimport express from "express";import { CortiAuth } from "@corti/sdk";const app = express();const CLIENT_ID = "YOUR_CLIENT_ID";const CLIENT_SECRET = "YOUR_CLIENT_SECRET";const TENANT_NAME = "YOUR_TENANT_NAME";const ENVIRONMENT = "YOUR_ENVIRONMENT";const REDIRECT_URI = "https://yourapp.com/callback"; // must match OAuth settingsapp.get("/callback", async (req, res) => { const authCode = req.query.code; if (!authCode) { return res.status(400).send("Missing authorization code"); } try { // Initialize CortiAuth SDK client const auth = new CortiAuth({ environment: ENVIRONMENT, tenantName: TENANT_NAME, }); // Exchange code for token const tokens = await auth.getCodeFlowToken({ clientId: CLIENT_ID, clientSecret: CLIENT_SECRET, redirectUri: REDIRECT_URI, code: authCode, }); // Example "do something": simply log the access token console.log("Access Token:", tokens.accessToken); // Redirect back to app with token in query param (example) res.json(tokens); } catch (err) { console.error("OAuth error:", err); return res.status(500).send("Failed to exchange authorization code"); }});app.listen(3000, () => { console.log("Server running at http://localhost:3000");});
This version assumes:
Your app has a frontend (e.g., React) that initiates the login.
Your app has a backend (e.g., Node.js + Express) that securely stores the client_secret and handles the token exchange.
Use this only if your backend can securely store the client secret (e.g. not in a browser or mobile app). Ideal for server-rendered or hybrid web apps.
Step 1: Frontend – Redirect the User to Log In
Copy
Ask AI
// login.tsx or similarconst clientId = 'YOUR_CLIENT_ID';const redirectUri = 'https://yourapp.com/callback'; // Must match what's registered in Corti OAuthconst login = () => {const authUrl = new URL('https://auth.us.corti.app/realms/<realm-name>/protocol/openid-connect/auth');authUrl.searchParams.set('response_type', 'code');authUrl.searchParams.set('client_id', clientId);authUrl.searchParams.set('redirect_uri', redirectUri);authUrl.searchParams.set('scope', 'openid profile');window.location.href = authUrl.toString(); // Send user to Corti login page};
Step 2: Corti Redirects Back with a Code
Copy
Ask AI
https://yourapp.com/callback?code=abc123xyz
Step 3: Backend – Exchange Code for Access Token
Copy
Ask AI
// server.js or routes/callback.jsconst express = require('express');const fetch = require('node-fetch');const app = express();const CLIENT_ID = 'YOUR_CLIENT_ID';const CLIENT_SECRET = 'YOUR_CLIENT_SECRET';const REDIRECT_URI = 'https://yourapp.com/callback'; // Must match Step 1app.get('/callback', async (req, res) => {const authCode = req.query.code;if (!authCode) { return res.status(400).send('Missing authorization code');}// Exchange code for access tokenconst tokenResponse = await fetch('https://auth.us.corti.app/realms/<realm-name>/protocol/openid-connect/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'authorization_code', code: authCode, client_id: CLIENT_ID, client_secret: CLIENT_SECRET, redirect_uri: REDIRECT_URI })});const tokenData = await tokenResponse.json();if (tokenData.error) { return res.status(500).send(`Token error: ${tokenData.error_description}`);}// Do something useful with the token, like create a sessionconsole.log('Access Token:', tokenData.access_token);// Optional: send token data to frontend or store in sessionres.redirect(`/app?token=${tokenData.access_token}`);});app.listen(3000, () => console.log('App listening on http://localhost:3000'));
This example uses standard .NET libraries for OAuth2 authorization code flow
Step 1: Frontend – Redirect the User to Log In
Copy
Ask AI
using System;using System.Web;public class AuthHelper{ private const string ClientId = "YOUR_CLIENT_ID"; private const string RedirectUri = "https://yourapp.com/callback"; private const string AuthBaseUrl = "https://auth.us.corti.app/realms/<realm-name>/protocol/openid-connect"; public static string GetAuthorizationUrl() { var authUrl = $"{AuthBaseUrl}/auth?" + $"response_type=code&" + $"client_id={HttpUtility.UrlEncode(ClientId)}&" + $"redirect_uri={HttpUtility.UrlEncode(RedirectUri)}&" + $"scope=openid profile"; return authUrl; }}// Usage in Razor view or controller// Response.Redirect(AuthHelper.GetAuthorizationUrl());
Step 2: Corti Redirects Back with a Code
Copy
Ask AI
https://yourapp.com/callback?code=abc123xyz
Step 3: Backend – Exchange Code for Access Token
Copy
Ask AI
using System;using System.Collections.Generic;using System.Net.Http;using System.Text.Json;using System.Threading.Tasks;using System.Web.Mvc;public class TokenExchange{ private const string ClientId = "YOUR_CLIENT_ID"; private const string ClientSecret = "YOUR_CLIENT_SECRET"; private const string RedirectUri = "https://yourapp.com/callback"; private const string TokenUrl = "https://auth.us.corti.app/realms/<realm-name>/protocol/openid-connect/token"; public static async Task<TokenResponse> ExchangeCodeForTokenAsync(string code) { using var httpClient = new HttpClient(); var formData = new List<KeyValuePair<string, string>> { new KeyValuePair<string, string>("grant_type", "authorization_code"), new KeyValuePair<string, string>("code", code), new KeyValuePair<string, string>("client_id", ClientId), new KeyValuePair<string, string>("client_secret", ClientSecret), new KeyValuePair<string, string>("redirect_uri", RedirectUri) }; var content = new FormUrlEncodedContent(formData); var response = await httpClient.PostAsync(TokenUrl, content); if (!response.IsSuccessStatusCode) { var errorBody = await response.Content.ReadAsStringAsync(); throw new Exception($"Token exchange failed: {errorBody}"); } var responseBody = await response.Content.ReadAsStringAsync(); var tokenData = JsonSerializer.Deserialize<JsonElement>(responseBody); if (tokenData.TryGetProperty("error", out var error)) { var errorDescription = tokenData.TryGetProperty("error_description", out var desc) ? desc.GetString() : "Unknown error"; throw new Exception($"Token error: {errorDescription}"); } return new TokenResponse { AccessToken = tokenData.GetProperty("access_token").GetString(), RefreshToken = tokenData.TryGetProperty("refresh_token", out var refresh) ? refresh.GetString() : null, ExpiresIn = tokenData.GetProperty("expires_in").GetInt32() }; }}public class TokenResponse{ public string AccessToken { get; set; } public string RefreshToken { get; set; } public int ExpiresIn { get; set; }}// Usage in ASP.NET MVC Controllerpublic class AuthController : Controller{ public async Task<ActionResult> Callback(string code) { if (string.IsNullOrEmpty(code)) { return new HttpStatusCodeResult(400, "Missing authorization code"); } try { var tokenResponse = await TokenExchange.ExchangeCodeForTokenAsync(code); // Store tokens securely (e.g., in session or secure cookie) Session["access_token"] = tokenResponse.AccessToken; // Redirect to app with token return RedirectToAction("Index", "Home"); } catch (Exception ex) { return new HttpStatusCodeResult(500, $"Token error: {ex.Message}"); } }}
Summary of the flow:
Step
Component
Description
1
Frontend
Redirects user to Corti login
2
Corti
Redirects back to your app with code
3
Backend
Exchanges code + client secret for tokens Responds with session/token, redirects to frontend
Requirements:
Must use HTTPS for redirect_uri
Your client_secretmust not be exposed to the frontend
The code returned is valid for one use and short-lived
Use only if the OAuth2 flow is entirely server-to-server.
3. Resource Owner Password Credentials (ROPC) Grant (Use with caution)
Best for: Controlled environments embedding Corti Assistant with trusted clients (e.g., internal tools).Why: Allows username/password login directly in the app — but bypasses the authorization server UI.Risks:
Trains users to enter passwords into third-party apps.
Easy to misuse, violates best practices.
Only viable where UI constraints prevent redirecting (e.g., native kiosk apps without browsers).
Important: Embedded Corti Assistant requires user-based authentication. Client credentials grant is NOT supported as it does not provide user context.
Pick the flow that matches your interaction model as offered above. See further details and contact us for support here.What to Use When:
For embedded Corti Assistant (e.g., in an iFrame/WebView): Use Authorization Code Flow with PKCE to authenticate the end user securely. This is the recommended flow for most embedded integrations.
For server-side integrations where you can securely store a client secret: Authorization Code Flow (without PKCE) may be used, but ensure the client secret is never exposed to the frontend.
For constrained environments without redirect capabilities: ROPC may be used with caution, but only in trusted internal environments.
Client Credentials is NOT supported — it does not provide user context and will not work with embedded Corti Assistant.