OAuth Authentication
MoonKey enables users to sign in using familiar OAuth providers like Google and Apple. OAuth authentication provides a seamless login experience that users are already comfortable with, integrated into your application in just a few lines of code.
Enable OAuth login methods in the MoonKey Dashboard under Login Methods before implementing this feature.
Google OAuth login may not work in in-app browsers (IABs), such as those embedded in social apps, due to Google’s restrictions in these environments. Apple OAuth and other providers are generally unaffected.
Supported OAuth Providers
MoonKey currently supports the following OAuth providers:
- Google - Sign in with Google accounts
- Apple - Sign in with Apple ID
Additional OAuth providers (Microsoft, Discord, GitHub, etc.) are coming soon. Contact us if you need support for a specific provider.
Configuration
Dashboard Setup
Before implementing OAuth authentication, you must configure your OAuth credentials for each provider:
- Log in to the MoonKey Dashboard
- Navigate to Login Methods → OAuth
- Select the provider you want to enable (Google or Apple)
- Enter your OAuth Client ID and Client Secret
- Configure your redirect URLs
- Save your changes
MoonKey does not provide default OAuth credentials. You must configure your own OAuth app credentials for each provider you want to support.
Getting OAuth Credentials
Google OAuth
- Go to the Google Cloud Console
- Create a new project or select an existing one
- Enable the Google+ API
- Go to Credentials → Create Credentials → OAuth 2.0 Client ID
- Configure the consent screen
- Add authorized redirect URIs provided by MoonKey
- Copy the Client ID and Client Secret to the MoonKey Dashboard
Apple OAuth
- Go to Apple Developer
- Navigate to Certificates, Identifiers & Profiles
- Create a new Services ID
- Enable Sign in with Apple
- Configure the redirect URLs
- Generate a private key
- Copy the credentials to the MoonKey Dashboard
Using the React SDK
MoonKey provides two ways to implement OAuth authentication:
useMoonKey hook with start() - Unified authentication flow with all login methods
useLoginWithOAuth hook - Dedicated OAuth hook for more control
Import
import { useMoonKey } from '@moon-key/react-auth';
// or for dedicated OAuth flow
import { useLoginWithOAuth } from '@moon-key/react-auth';
Method 1: Using useMoonKey
The useMoonKey hook provides a unified way to trigger authentication with multiple methods.
Basic Usage
Open the authentication modal with OAuth providers:
import { useMoonKey } from '@moon-key/react-auth';
export default function OAuthLogin() {
const { start, isAuthenticated, user } = useMoonKey();
if (isAuthenticated) {
return <div>Welcome, {user.email || user.google?.email}!</div>;
}
return (
<button onClick={() => start()}>
Login
</button>
);
}
Google-only Login
Show only Google authentication:
import { useMoonKey } from '@moon-key/react-auth';
export default function GoogleLogin() {
const { start } = useMoonKey();
return (
<button onClick={() => start({ loginMethods: ['google'] })}>
Sign in with Google
</button>
);
}
Apple-only Login
Show only Apple authentication:
import { useMoonKey } from '@moon-key/react-auth';
export default function AppleLogin() {
const { start } = useMoonKey();
return (
<button onClick={() => start({ loginMethods: ['apple'] })}>
Sign in with Apple
</button>
);
}
Multiple OAuth Providers
Show multiple OAuth options:
import { useMoonKey } from '@moon-key/react-auth';
export default function SocialLogin() {
const { start } = useMoonKey();
return (
<button onClick={() => start({ loginMethods: ['google', 'apple'] })}>
Continue with Social Login
</button>
);
}
Combined with Other Methods
Combine OAuth with email and wallet authentication:
import { useMoonKey } from '@moon-key/react-auth';
export default function AllLoginMethods() {
const { start } = useMoonKey();
return (
<button onClick={() => start({
loginMethods: ['email', 'google', 'apple', 'wallet'],
walletChainType: 'ethereum-or-solana'
})}>
Login or Sign Up
</button>
);
}
Login-only (Disable Signup)
Prevent new user signups via OAuth:
import { useMoonKey } from '@moon-key/react-auth';
export default function ExistingUsersOnly() {
const { start } = useMoonKey();
return (
<button onClick={() => start({
loginMethods: ['google', 'apple'],
disableSignup: true
})}>
Sign in (Existing Users)
</button>
);
}
Method 2: Using useLoginWithOAuth
The useLoginWithOAuth hook provides dedicated OAuth functionality with more control over the authentication flow.
Import and Usage
import { useLoginWithOAuth } from '@moon-key/react-auth';
export default function LoginWithOAuth() {
const { loginWithOAuth, state } = useLoginWithOAuth();
const handleGoogleLogin = async () => {
try {
await loginWithOAuth({ provider: 'google' });
// User is now authenticated
} catch (error) {
console.error('Login failed:', error);
}
};
return (
<button onClick={handleGoogleLogin} disabled={state.status === 'loading'}>
{state.status === 'loading' ? 'Logging in...' : 'Sign in with Google'}
</button>
);
}
loginWithOAuth Method
Initiates the OAuth authentication flow for a specific provider:
loginWithOAuth: ({
provider: 'google' | 'apple',
disableSignup?: boolean
}) => Promise<void>
Parameters
The OAuth provider to use. Options: 'google' or 'apple'.
If true, prevents new user signups and only allows existing users to login.
Tracking Flow State
Track the OAuth authentication state:
state:
| { status: 'initial' }
| { status: 'loading' }
| { status: 'done' }
| { status: 'error'; error: Error | null }
status
'initial' | 'loading' | 'done' | 'error'
The current state of the OAuth flow.
The error that occurred during the OAuth flow (only present when status is ‘error’).
Complete Example
import { useLoginWithOAuth, useMoonKey } from '@moon-key/react-auth';
import { useState } from 'react';
export default function OAuthButtons() {
const { loginWithOAuth, state } = useLoginWithOAuth();
const { isAuthenticated, user } = useMoonKey();
const [error, setError] = useState<string | null>(null);
const handleLogin = async (provider: 'google' | 'apple') => {
setError(null);
try {
await loginWithOAuth({ provider });
} catch (err) {
setError(err instanceof Error ? err.message : 'Login failed');
}
};
if (isAuthenticated) {
return <div>Welcome, {user.email}!</div>;
}
return (
<div>
<button
onClick={() => handleLogin('google')}
disabled={state.status === 'loading'}
>
{state.status === 'loading' ? 'Signing in...' : 'Sign in with Google'}
</button>
<button
onClick={() => handleLogin('apple')}
disabled={state.status === 'loading'}
>
{state.status === 'loading' ? 'Signing in...' : 'Sign in with Apple'}
</button>
{state.status === 'error' && (
<p style={{ color: 'red' }}>Error: {state.error?.message}</p>
)}
{error && <p style={{ color: 'red' }}>{error}</p>}
</div>
);
}
With Callbacks
You can pass callbacks to handle success and error cases:
import { useLoginWithOAuth } from '@moon-key/react-auth';
export default function OAuthWithCallbacks() {
const { loginWithOAuth } = useLoginWithOAuth({
onSuccess: ({ user, isNewUser }) => {
console.log('User logged in:', user);
if (isNewUser) {
// Handle new user onboarding
console.log('Welcome new user!');
}
},
onError: (error) => {
console.error('OAuth login failed:', error);
// Show error notification
}
});
return (
<button onClick={() => loginWithOAuth({ provider: 'google' })}>
Sign in with Google
</button>
);
}
Login-only Mode
Prevent new signups and only allow existing users:
import { useLoginWithOAuth } from '@moon-key/react-auth';
export default function ExistingUsersOnly() {
const { loginWithOAuth } = useLoginWithOAuth();
return (
<button
onClick={() => loginWithOAuth({
provider: 'google',
disableSignup: true
})}
>
Sign in (Existing Users Only)
</button>
);
}
Provider Configuration
You can configure which OAuth providers are available by default in the MoonKeyProvider:
import { MoonKeyProvider } from '@moon-key/react-auth';
export default function App() {
return (
<MoonKeyProvider
publishableKey="your_publishable_key"
config={{
loginMethods: ['google', 'apple', 'email'],
appearance: {
logo: 'https://your-app.com/logo.png',
loginHeaderTitle: 'Welcome back',
loginHeaderDescription: 'Sign in to continue'
}
}}
>
{/* Your app */}
</MoonKeyProvider>
);
}
Using the REST API
For backend implementations or custom integrations, use the MoonKey REST API.
Initiate OAuth Flow
Start the OAuth authentication flow:
curl -X GET "https://api.moonkey.fun/v1/auth/oauth/google/begin" \
-H "Authorization: Bearer sk_test_your_api_key"
This returns a redirect URL where you should send the user:
{
"url": "https://accounts.google.com/o/oauth2/v2/auth?client_id=...",
"state": "random_state_value"
}
Verify OAuth Token
After the user completes OAuth authentication and is redirected back to your application, verify the OAuth code:
curl -X POST "https://api.moonkey.fun/v1/auth/oauth/verify" \
-H "Authorization: Bearer sk_test_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"code": "oauth_authorization_code",
"state": "random_state_value",
"session_expires_in": 10080
}'
Response
{
"session_token": "session_abc123...",
"session_jwt": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": "user_xyz789",
"email": "user@example.com",
"google": {
"email": "user@gmail.com",
"name": "John Doe",
"subject": "google_user_id"
},
"created_at": 1704067200,
"updated_at": 1704067200
},
"session": {
"id": "session_abc123",
"user_id": "user_xyz789",
"expires_at": 1704672000,
"created_at": 1704067200
}
}
Backend Implementation Example
// Express.js example
import express from 'express';
const app = express();
// Initiate OAuth flow
app.get('/auth/google', async (req, res) => {
try {
const response = await fetch(
'https://api.moonkey.fun/v1/auth/oauth/google/begin',
{
headers: {
'Authorization': `Bearer ${process.env.MOONKEY_SECRET_KEY}`
}
}
);
const data = await response.json();
// Store state in session for verification
req.session.oauthState = data.state;
// Redirect user to Google
res.redirect(data.url);
} catch (error) {
res.status(500).json({ error: 'Failed to initiate OAuth' });
}
});
// OAuth callback endpoint
app.get('/auth/callback', async (req, res) => {
const { code, state } = req.query;
// Verify state matches
if (state !== req.session.oauthState) {
return res.status(400).json({ error: 'Invalid state parameter' });
}
try {
const response = await fetch(
'https://api.moonkey.fun/v1/auth/oauth/verify',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.MOONKEY_SECRET_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
code,
state,
session_expires_in: 10080 // 7 days
})
}
);
if (!response.ok) {
throw new Error('OAuth verification failed');
}
const data = await response.json();
// Store session token in cookie or return to client
res.cookie('session_token', data.session_token, {
httpOnly: true,
secure: true,
maxAge: 7 * 24 * 60 * 60 * 1000 // 7 days
});
// Redirect to your app
res.redirect('/dashboard');
} catch (error) {
res.status(500).json({ error: 'OAuth verification failed' });
}
});
Security Best Practices
Redirect URL Validation
Configure allowed OAuth redirect URLs in the MoonKey Dashboard to restrict where users can be redirected after authentication:
- Navigate to Redirect URLs in the dashboard
- Add your application’s callback URLs
- Only whitelisted URLs will be accepted
Always validate redirect URLs to prevent open redirect vulnerabilities.
State Parameter
The state parameter helps prevent CSRF attacks:
- MoonKey generates a unique state value for each OAuth flow
- Store this value in your session
- Verify it matches when handling the callback
- Reject requests with mismatched state values
HTTPS Only
- Always use HTTPS in production
- Configure OAuth providers to only accept HTTPS redirect URLs
- Never use HTTP for OAuth flows
Token Storage
- Store session tokens securely (HttpOnly cookies on web)
- Never expose session tokens in URLs or logs
- Use secure, httpOnly cookies when possible
User Experience
Loading States
Show appropriate loading states during OAuth flow:
import { useMoonKey } from '@moon-key/react-auth';
import { useState } from 'react';
export default function OAuthLoginButton() {
const { start, isAuthenticated } = useMoonKey();
const [loading, setLoading] = useState(false);
const handleLogin = async () => {
setLoading(true);
try {
await start({ loginMethods: ['google'] });
} finally {
setLoading(false);
}
};
if (isAuthenticated) {
return <div>Logged in!</div>;
}
return (
<button onClick={handleLogin} disabled={loading}>
{loading ? 'Redirecting...' : 'Sign in with Google'}
</button>
);
}
Error Handling
The MoonKey SDK automatically handles common OAuth errors:
- User cancellation - When users close the OAuth popup or decline
- Invalid credentials - When OAuth configuration is incorrect
- Network errors - Connection issues during the flow
- State mismatch - CSRF protection validation failures
MoonKey uses popup windows for OAuth flows by default, which:
- Keep users on your page
- Provide a seamless experience
- Work across all modern browsers
For mobile or in-app browsers, MoonKey automatically falls back to redirect-based flows.
Testing
Test Mode
During development, use test API keys to:
- Test OAuth flows without affecting production users
- Validate your integration
- Debug callback handling
Test with Real Providers
Always test with actual OAuth providers:
- Google test accounts
- Apple test accounts
- Verify the complete flow works end-to-end
Common Test Scenarios
- First-time user signup via OAuth
- Existing user login via OAuth
- User cancels OAuth flow
- Network errors during OAuth
- Invalid or expired OAuth codes
- Multiple OAuth accounts linked to same user
Troubleshooting
OAuth flow doesn’t start
Possible causes:
- OAuth provider not enabled in dashboard
- Invalid OAuth credentials configured
- Missing redirect URLs in dashboard
Solutions:
- Verify provider is enabled in Login Methods
- Check OAuth Client ID and Secret are correct
- Add your callback URLs to Redirect URLs
Redirect URL mismatch error
Cause: The redirect URL doesn’t match what’s configured in your OAuth provider.
Solution:
- Check the redirect URL in MoonKey Dashboard
- Ensure it matches exactly in your OAuth provider settings (Google Cloud Console, Apple Developer)
- Include the protocol (
https://) and any path components
State parameter mismatch
Cause: The state value doesn’t match between initial request and callback.
Solutions:
- Ensure you’re storing the state in your session correctly
- Check for session timeout issues
- Verify cookies are enabled
Cause: OAuth scopes might not include profile information.
Solution:
Check your OAuth provider configuration includes the necessary scopes:
- Google:
email, profile, openid
- Apple:
email, name
Provider-Specific Notes
Google OAuth
- Supports email and profile information
- Works on web and most browsers
- May not work in some in-app browsers (Facebook, Instagram apps)
- Requires verified domain for production use
Apple OAuth
- Requires Apple Developer account
- Email may be hidden/private on first login
- Name is only provided on first authentication
- Works across web, iOS, and macOS