Skip to main content

Introduction to Keyban Auth

Keyban Auth is a flexible authentication layer that lets you manage access to non-custodial wallets - with or without your own identity system.

Keyban Auth overview

Keyban Auth is a secure and modular authentication layer designed to simplify the way users access non-custodial wallets.
It enables integrators to offer custom login experiences while benefiting from high-security key management and seamless wallet orchestration.

Whether you need a branded login interface, integration with an existing SSO, or support for modern authentication flows like social login (e.g., Google) or OTP, Keyban Auth adapts to your needs with minimal integration effort.

You can fully customize the login interface using secure components like KeybanInput, which ensures sensitive data is isolated in secure iframes, and useKeybanAuth hook for authentication logic, adapting them to your design system and branding while maintaining the highest security standards.


Developer Entry Points

To implement authentication in your React app, use:

  • useKeybanAuth – to manage authentication state, session lifecycle, and access to the wallet.
  • KeybanInput – secure iframe-based input component for handling sensitive authentication data.

Both are available via the Keyban SDK for React and compatible with React Native.


Authentication Recipes

This section provides a collection of recipes for common authentication patterns.

Secure Phone-based Login with OTP

This example shows a two-step phone OTP flow with loading and error states.

import { KeybanInput, useKeybanAuth } from "@keyban/sdk-react";
import { useState } from "react";

export default function PhoneOTPLogin() {
const [step, setStep] = useState<"phone" | "otp">("phone");
const { sendOtp, signIn, isAuthenticated, isLoading } = useKeybanAuth();
const [error, setError] = useState<string | null>(null);

const handlePhoneSubmit = async () => {
setError(null);
try {
await sendOtp({
type: "phone-otp",
phoneCallingCode: "33", // France country code
phoneInputName: "phone",
});
setStep("otp");
} catch (err) {
setError("Failed to send OTP. Please check the phone number and try again.");
console.error(err);
}
};

const handleOtpSubmit = async () => {
setError(null);
try {
await signIn({
type: "phone-otp",
phoneCallingCode: "33",
phoneInputName: "phone",
otpInputName: "otp",
});
} catch (err) {
setError("Invalid OTP. Please try again.");
console.error(err);
}
};

if (isAuthenticated) return <div>You're in!</div>;

return (
<form onSubmit={(e) => e.preventDefault()}>
{error && <div style={{ color: "red" }}>{error}</div>}
{step === "phone" ? (
<div>
<label>Phone Number:</label>
<KeybanInput name="phone" type="tel" />
<button type="button" onClick={handlePhoneSubmit} disabled={isLoading}>
{isLoading ? "Sending..." : "Send Code"}
</button>
</div>
) : (
<div>
<label>Verification Code:</label>
<KeybanInput
name="otp"
inputMode="numeric"
inputStyles={{ textAlign: "center" }}
/>
<button type="button" onClick={handleOtpSubmit} disabled={isLoading}>
{isLoading ? "Verifying..." : "Verify"}
</button>
</div>
)}
</form>
);
}

Email/Password Login

This example demonstrates a standard email and password login form, including loading state and error handling.

import { KeybanInput, useKeybanAuth } from "@keyban/sdk-react";
import { useState } from "react";

export default function EmailLogin() {
// Get the signIn function and loading state from the auth hook
const { signIn, isLoading } = useKeybanAuth();
const [error, setError] = useState<string | null>(null);

const handleLogin = async () => {
setError(null); // Reset error state on new submission
try {
// Call signIn with the appropriate type and input names
await signIn({
type: "password",
usernameInputName: "email",
passwordInputName: "password",
});
// On success, the user will be authenticated and the component will re-render
} catch (err) {
// If signIn fails, it will throw an error
setError("Invalid email or password. Please try again.");
console.error(err);
}
};

return (
<form onSubmit={(e) => e.preventDefault()}>
{error && <div style={{ color: "red" }}>{error}</div>}
<div>
<label>Email:</label>
<KeybanInput name="email" type="email" />
</div>
<div>
<label>Password:</label>
<KeybanInput name="password" type="password" />
</div>
<button type="button" onClick={handleLogin} disabled={isLoading}>
{isLoading ? "Logging in..." : "Login"}
</button>
</form>
);
}

Email OTP Authentication

This example shows a two-step email OTP flow with loading and error states.

import { KeybanInput, useKeybanAuth } from "@keyban/sdk-react";
import { useState } from "react";

export default function EmailOTPLogin() {
const [showOtp, setShowOtp] = useState(false);
const { sendOtp, signIn, isLoading } = useKeybanAuth();
const [error, setError] = useState<string | null>(null);

const handleSendCode = async () => {
setError(null);
try {
await sendOtp({
type: "email-otp",
emailInputName: "email",
});
setShowOtp(true);
} catch (err) {
setError("Failed to send OTP. Please check the email and try again.");
console.error(err);
}
};

const handleVerifyCode = async () => {
setError(null);
try {
await signIn({
type: "email-otp",
emailInputName: "email",
otpInputName: "otp",
});
} catch (err) {
setError("Invalid OTP. Please try again.");
console.error(err);
}
};

return (
<form onSubmit={(e) => e.preventDefault()}>
{error && <div style={{ color: "red" }}>{error}</div>}

{!showOtp ? (
<div>
<label>Email:</label>
<KeybanInput name="email" type="email" />
</div>
) : (
<div>
<label>Verification Code:</label>
<KeybanInput
name="otp"
inputMode="numeric"
inputStyles={{ textAlign: "center" }}
/>
</div>
)}

<button
type="button"
onClick={showOtp ? handleVerifyCode : handleSendCode}
disabled={isLoading}
>
{isLoading
? "Processing..."
: (showOtp ? "Verify Code" : "Send Code")}
</button>
</form>
);
}

User Sign-Up

To register a new user, use the signUp function. You can include additional user metadata during registration. This example includes loading and error handling.

import { KeybanInput, useKeybanAuth } from "@keyban/sdk-react";
import { useState } from "react";

export default function SignUp() {
const { signUp, isLoading } = useKeybanAuth();
const [error, setError] = useState<string | null>(null);

const handleSignUp = async () => {
setError(null);
try {
await signUp({
usernameInputName: "email",
passwordInputName: "password",
userMetadata: {
firstName: "John",
lastName: "Doe",
},
});
} catch (err) {
setError("Failed to sign up. Please try again.");
console.error(err);
}
};

return (
<form onSubmit={(e) => e.preventDefault()}>
{error && <div style={{ color: "red" }}>{error}</div>}
<div>
<label>Email:</label>
<KeybanInput name="email" type="email" />
</div>
<div>
<label>Password:</label>
<KeybanInput name="password" type="password" />
</div>
<button type="button" onClick={handleSignUp} disabled={isLoading}>
{isLoading ? "Signing up..." : "Sign Up"}
</button>
</form>
);
}

Google Social Login

You can initiate a social login flow with Google by calling signIn with the type google.

import { useKeybanAuth } from "@keyban/sdk-react";
import { useState } from "react";

export default function GoogleLogin() {
const { signIn, isLoading } = useKeybanAuth();
const [error, setError] = useState<string | null>(null);

const handleGoogleLogin = async () => {
setError(null);
try {
await signIn({
type: "google",
});
} catch (err) {
setError("Failed to sign in with Google. Please try again.");
console.error(err);
}
};

return (
<>
{error && <div style={{ color: "red" }}>{error}</div>}
<button type="button" onClick={handleGoogleLogin} disabled={isLoading}>
{isLoading ? "Signing in..." : "Sign in with Google"}
</button>
</>
);
}

User Sign-Out

To log out a user, simply call the signOut function.

import { useKeybanAuth } from "@keyban/sdk-react";

export default function UserProfile() {
const { user, signOut } = useKeybanAuth();

if (!user) return null;

return (
<div>
<p>Welcome, {user.email}!</p>
<button onClick={() => signOut()}>
Sign Out
</button>
</div>
);
}

Updating User Information

You can update a user's metadata with the updateUser function. This example shows how to update the user's company and handles loading and error states.

import { useKeybanAuth } from "@keyban/sdk-react";
import { useState } from "react";

export default function UpdateProfile() {
const { user, updateUser, isLoading } = useKeybanAuth();
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState<string | null>(null);

const handleUpdate = async () => {
setError(null);
setSuccess(null);
if (user) {
try {
await updateUser({
userMetadata: {
...user.userMetadata,
company: "Keyban Inc.",
},
});
setSuccess("Profile updated successfully!");
} catch (err) {
setError("Failed to update profile. Please try again.");
console.error(err);
}
}
};

return (
<div>
{error && <div style={{ color: "red" }}>{error}</div>}
{success && <div style={{ color: "green" }}>{success}</div>}
<p>Current Company: {user?.userMetadata?.company || "Not set"}</p>
<button type="button" onClick={handleUpdate} disabled={isLoading}>
{isLoading ? "Updating..." : "Update Company"}
</button>
</div>
);
}

Material-UI Integration

import { KeybanInput, useKeybanAuth } from "@keyban/sdk-react";
import { TextField, Button, Box } from "@mui/material";
import { useState } from "react";

export default function MaterialUILogin() {
const [showOtp, setShowOtp] = useState(false);
const { sendOtp, signIn } = useKeybanAuth();

const handleSendCode = async () => {
await sendOtp({
type: "email-otp",
emailInputName: "email",
});
setShowOtp(true);
};

const handleVerifyCode = async () => {
await signIn({
type: "email-otp",
emailInputName: "email",
otpInputName: "otp",
});
};

return (
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
<TextField
label="Email"
variant="outlined"
fullWidth
slots={{
htmlInput: () => (
<Box
component={KeybanInput}
name="email"
type="email"
sx={{ flexGrow: 1 }}
/>
),
}}
/>

{showOtp && (
<TextField
label="Verification Code"
variant="outlined"
fullWidth
slots={{
htmlInput: () => (
<Box
component={KeybanInput}
name="otp"
inputMode="numeric"
inputStyles={{ textAlign: "center" }}
sx={{ flexGrow: 1 }}
/>
),
}}
/>
)}

<Button
variant="contained"
onClick={showOtp ? handleVerifyCode : handleSendCode}
>
{showOtp ? "Verify Code" : "Continue with Email"}
</Button>
</Box>
);
}

International Phone Number Input

import { KeybanInput, useKeybanAuth } from "@keyban/sdk-react";
import { MuiTelInput } from "mui-tel-input";
import { useState } from "react";

const PhoneInput = () => (
<Box
component={KeybanInput}
type="tel"
name="phone"
sx={{ flexGrow: 1, p: 2, pl: 0 }}
/>
);

export default function PhoneLogin() {
const [phoneCallingCode, setPhoneCallingCode] = useState("33");
const [showOtp, setShowOtp] = useState(false);
const { sendOtp, signIn } = useKeybanAuth();

const handleSendCode = async () => {
await sendOtp({
type: "phone-otp",
phoneCallingCode,
phoneInputName: "phone",
});
setShowOtp(true);
};

const handleVerifyCode = async () => {
await signIn({
type: "phone-otp",
phoneCallingCode,
phoneInputName: "phone",
otpInputName: "otp",
});
};

return (
<div>
<MuiTelInput
name="phone"
defaultCountry="FR"
value={`+${phoneCallingCode}`}
forceCallingCode
onChange={(_, infos) =>
setPhoneCallingCode(infos.countryCallingCode ?? "")
}
fullWidth
slots={{ htmlInput: PhoneInput }}
/>

{showOtp && (
<KeybanInput
name="otp"
inputMode="numeric"
inputStyles={{ textAlign: "center" }}
/>
)}

<button onClick={showOtp ? handleVerifyCode : handleSendCode}>
{showOtp ? "Verify" : "Continue"}
</button>
</div>
);
}

The KeybanInput Component

The KeybanInput component is a secure, iframe-based input designed specifically for handling sensitive authentication data. It provides a seamless way to collect user credentials while maintaining the highest security standards.

Key Security Features

  • Iframe Isolation: All inputs are rendered in sandboxed iframes to prevent data leakage
  • Cross-Origin Protection: Secure communication between iframe and parent application
  • No Direct Access: Input values are never accessible from the parent application context

KeybanInput Advanced Features

Focus Management

import { KeybanInput, KeybanInputRef } from "@keyban/sdk-react";
import { useRef } from "react";

export default function FocusExample() {
const emailRef = useRef<KeybanInputRef>(null);
const passwordRef = useRef<KeybanInputRef>(null);

const focusEmail = () => emailRef.current?.focus();
const focusPassword = () => passwordRef.current?.focus();

return (
<div>
<KeybanInput ref={emailRef} name="email" type="email" />
<KeybanInput ref={passwordRef} name="password" type="password" />

<button onClick={focusEmail}>Focus Email</button>
<button onClick={focusPassword}>Focus Password</button>
</div>
);
}

Custom Styling

import { KeybanInput } from "@keyban/sdk-react";

export default function StyledInputs() {
return (
<div>
{/* OTP input with centered text */}
<KeybanInput
name="otp"
inputMode="numeric"
inputStyles={{
textAlign: "center",
fontSize: "24px",
backgroundColor: "#f5f5f5",
color: "#333",
}}
style={{
border: "2px solid #007bff",
borderRadius: "8px",
padding: "12px",
}}
/>

{/* Email input with custom appearance */}
<KeybanInput
name="email"
type="email"
inputStyles={{
fontSize: "16px",
color: "#2c3e50",
}}
className="custom-input-class"
/>
</div>
);
}

Best Practices

  1. Always use KeybanInput for sensitive data: Never use regular HTML inputs for passwords, OTP codes, or other sensitive information to ensure that sensitive data is handled securely within an iframe.

  2. Proper naming: Use descriptive and consistent names for your inputs that match your authentication flow (e.g., "email", "phone", "otp"). These names are used to reference the inputs in the useKeybanAuth functions.

  3. Use correct input types: Always specify the appropriate type for your KeybanInput components (e.g., email, tel, password) to provide the best user experience on different devices (e.g., showing the correct keyboard).

  4. Implement robust error handling: Authentication can fail for many reasons (e.g., invalid credentials, network issues). Always wrap your authentication calls in a try...catch block and provide clear feedback to the user.

    const [error, setError] = useState<string | null>(null);

    const handleLogin = async () => {
    setError(null);
    try {
    await signIn({ type: "password", ... });
    } catch (err) {
    setError("Authentication failed. Please try again.");
    console.error(err);
    }
    };
  5. Provide loading state feedback: Authentication requests are asynchronous. Use the isLoading state from useKeybanAuth to give users visual feedback, such as disabling buttons or showing a spinner.

    const { signIn, isLoading } = useKeybanAuth();

    <button onClick={handleLogin} disabled={isLoading}>
    {isLoading ? "Logging in..." : "Login"}
    </button>
  6. Ensure accessibility:

    • Always associate a <label> with your KeybanInput components, even if the label is visually hidden.
    • Ensure that your forms are keyboard-navigable.
    • Use semantic HTML (<form>, <button>, etc.) to improve accessibility.