Skip to main content

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:
  1. Log in to the MoonKey Dashboard
  2. Navigate to Login MethodsOAuth
  3. Select the provider you want to enable (Google or Apple)
  4. Enter your OAuth Client ID and Client Secret
  5. Configure your redirect URLs
  6. 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

  1. Go to the Google Cloud Console
  2. Create a new project or select an existing one
  3. Enable the Google+ API
  4. Go to CredentialsCreate CredentialsOAuth 2.0 Client ID
  5. Configure the consent screen
  6. Add authorized redirect URIs provided by MoonKey
  7. Copy the Client ID and Client Secret to the MoonKey Dashboard

Apple OAuth

  1. Go to Apple Developer
  2. Navigate to Certificates, Identifiers & Profiles
  3. Create a new Services ID
  4. Enable Sign in with Apple
  5. Configure the redirect URLs
  6. Generate a private key
  7. Copy the credentials to the MoonKey Dashboard

Using the React SDK

MoonKey provides two ways to implement OAuth authentication:
  1. useMoonKey hook with start() - Unified authentication flow with all login methods
  2. 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

provider
string
required
The OAuth provider to use. Options: 'google' or 'apple'.
disableSignup
boolean
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.
error
Error | null
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:
  1. Navigate to Redirect URLs in the dashboard
  2. Add your application’s callback URLs
  3. 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:
  1. Verify provider is enabled in Login Methods
  2. Check OAuth Client ID and Secret are correct
  3. 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:
  1. Check the redirect URL in MoonKey Dashboard
  2. Ensure it matches exactly in your OAuth provider settings (Google Cloud Console, Apple Developer)
  3. 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

User information not available

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