Sign in with SoapBox

Allow users to sign in to your application using their SoapBox account. Implement OAuth 2.0 authentication to provide a seamless, secure login experience.

Overview

"Sign in with SoapBox" is an OAuth 2.0-based authentication system that enables third-party applications to authenticate users with their existing SoapBox accounts. This eliminates the need for users to create new accounts and remember additional passwords.

Benefits

  • Simplified onboarding - Users can sign in with one click using their existing SoapBox account
  • Increased trust - Users trust familiar login providers over creating new accounts
  • Secure authentication - Leverage SoapBox's secure authentication infrastructure
  • Access to user data - With user consent, access profile information like name and email
  • Reduced friction - No password fatigue or forgotten credentials

Registration

Before implementing "Sign in with SoapBox", you need to register your application to obtain client credentials.

Getting Client Credentials

  1. Sign in to the Developer Dashboard
  2. Create a new application or select an existing one
  3. Navigate to the OAuth Settings section
  4. Add your authorized redirect URIs
  5. Copy your client_id and client_secret

Security Warning

Keep your client_secret confidential. Never expose it in client-side code or public repositories. For single-page applications, use PKCE instead.

Authorization Flow

The authorization flow follows the OAuth 2.0 Authorization Code grant type. Here's how it works:

Step 1: Redirect to Authorization

Redirect the user to the SoapBox authorization endpoint with the required parameters:

https://soapboxsuperapp.com/oauth/authorize?
  client_id=YOUR_CLIENT_ID
  &redirect_uri=https://yourapp.com/callback
  &response_type=code
  &scope=openid profile email
  &state=random_state_string

Parameters

ParameterRequiredDescription
client_idYesYour application's client ID from the dashboard
redirect_uriYesURL to redirect back to after authorization. Must match a registered URI.
response_typeYesMust be code for the authorization code flow
scopeYesSpace-separated list of scopes (e.g., openid profile email)
stateRecommendedRandom string to prevent CSRF attacks. Returned unchanged in the callback.

Step 2: User Login and Consent

The user will be presented with the SoapBox login page (if not already logged in) and a consent screen showing what data your application is requesting access to. Once the user approves, they will be redirected back to your application.

Step 3: Handle the Callback

After the user authorizes your application, SoapBox redirects them back to yourredirect_uri with an authorization code:

https://yourapp.com/callback?code=AUTH_CODE&state=random_state_string

Important

Always verify that the state parameter matches the one you sent in Step 1 to prevent CSRF attacks.

Token Exchange

Exchange the authorization code for an access token by making a POST request to the token endpoint:

Request

POST https://soapboxsuperapp.com/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=AUTH_CODE
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
&redirect_uri=https://yourapp.com/callback

Response

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "dGhpcyBpcyBhIHJlZnJlc2g...",
  "scope": "openid profile email",
  "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
}
FieldDescription
access_tokenToken to access protected resources
token_typeAlways "Bearer"
expires_inToken lifetime in seconds (typically 3600)
refresh_tokenToken to obtain new access tokens
scopeGranted scopes
id_tokenJWT containing user identity claims (when openid scope is requested)

Get User Info

Retrieve the authenticated user's profile information using the access token:

Request

GET https://soapboxsuperapp.com/oauth/userinfo
Authorization: Bearer ACCESS_TOKEN

Response

{
  "sub": "user_123456",
  "name": "John Doe",
  "given_name": "John",
  "family_name": "Doe",
  "email": "john.doe@example.com",
  "email_verified": true,
  "picture": "https://soapboxsuperapp.com/avatars/user_123456.jpg"
}

Available Scopes

Scopes determine what user information your application can access. Request only the scopes you need.

ScopeDescriptionClaims Provided
openidRequired for OpenID Connect. Returns an ID token.sub
profileAccess to basic profile informationname, given_name, family_name, picture
emailAccess to the user's email addressemail, email_verified

Code Examples

JavaScript/TypeScript - Complete Flow

// Configuration
const config = {
  clientId: 'YOUR_CLIENT_ID',
  clientSecret: 'YOUR_CLIENT_SECRET', // Server-side only!
  redirectUri: 'https://yourapp.com/callback',
  authorizationEndpoint: 'https://soapboxsuperapp.com/oauth/authorize',
  tokenEndpoint: 'https://soapboxsuperapp.com/oauth/token',
  userinfoEndpoint: 'https://soapboxsuperapp.com/oauth/userinfo'
};

// Step 1: Generate authorization URL
function getAuthorizationUrl(): string {
  const state = crypto.randomUUID();
  // Store state in session for verification
  sessionStorage.setItem('oauth_state', state);

  const params = new URLSearchParams({
    client_id: config.clientId,
    redirect_uri: config.redirectUri,
    response_type: 'code',
    scope: 'openid profile email',
    state: state
  });

  return `${config.authorizationEndpoint}?${params.toString()}`;
}

// Redirect user to SoapBox login
function signInWithSoapBox(): void {
  window.location.href = getAuthorizationUrl();
}

Handle Callback (Server-side)

// Express.js example
import express from 'express';

const app = express();

app.get('/callback', async (req, res) => {
  const { code, state } = req.query;

  // Verify state to prevent CSRF
  const storedState = req.session.oauth_state;
  if (state !== storedState) {
    return res.status(403).json({ error: 'Invalid state parameter' });
  }

  try {
    // Exchange code for tokens
    const tokenResponse = await fetch(config.tokenEndpoint, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
      },
      body: new URLSearchParams({
        grant_type: 'authorization_code',
        code: code as string,
        client_id: config.clientId,
        client_secret: config.clientSecret,
        redirect_uri: config.redirectUri
      })
    });

    const tokens = await tokenResponse.json();

    if (!tokenResponse.ok) {
      throw new Error(tokens.error_description || 'Token exchange failed');
    }

    // Get user info
    const userResponse = await fetch(config.userinfoEndpoint, {
      headers: {
        'Authorization': `Bearer ${tokens.access_token}`
      }
    });

    const user = await userResponse.json();

    // Create session or JWT for your app
    req.session.user = {
      id: user.sub,
      name: user.name,
      email: user.email,
      picture: user.picture
    };

    res.redirect('/dashboard');

  } catch (error) {
    console.error('OAuth error:', error);
    res.redirect('/login?error=auth_failed');
  }
});

React Component Example

import { useState } from 'react';

function SignInButton() {
  const [isLoading, setIsLoading] = useState(false);

  const handleSignIn = () => {
    setIsLoading(true);

    const state = crypto.randomUUID();
    sessionStorage.setItem('oauth_state', state);

    const params = new URLSearchParams({
      client_id: process.env.NEXT_PUBLIC_SOAPBOX_CLIENT_ID!,
      redirect_uri: `${window.location.origin}/api/auth/callback`,
      response_type: 'code',
      scope: 'openid profile email',
      state: state
    });

    window.location.href = `https://soapboxsuperapp.com/oauth/authorize?${params}`;
  };

  return (
    <button
      onClick={handleSignIn}
      disabled={isLoading}
      className="flex items-center gap-2 px-4 py-2 bg-orange-500 text-white rounded-lg hover:bg-orange-600 disabled:opacity-50"
    >
      {isLoading ? (
        <span>Redirecting...</span>
      ) : (
        <>
          <SoapBoxLogo className="w-5 h-5" />
          <span>Sign in with SoapBox</span>
        </>
      )}
    </button>
  );
}

Next.js API Route Example

// app/api/auth/callback/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { cookies } from 'next/headers';

export async function GET(request: NextRequest) {
  const searchParams = request.nextUrl.searchParams;
  const code = searchParams.get('code');
  const state = searchParams.get('state');

  // Exchange code for tokens
  const tokenResponse = await fetch('https://soapboxsuperapp.com/oauth/token', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    body: new URLSearchParams({
      grant_type: 'authorization_code',
      code: code!,
      client_id: process.env.SOAPBOX_CLIENT_ID!,
      client_secret: process.env.SOAPBOX_CLIENT_SECRET!,
      redirect_uri: `${process.env.NEXT_PUBLIC_APP_URL}/api/auth/callback`
    })
  });

  const tokens = await tokenResponse.json();

  // Get user info
  const userResponse = await fetch('https://soapboxsuperapp.com/oauth/userinfo', {
    headers: {
      'Authorization': `Bearer ${tokens.access_token}`
    }
  });

  const user = await userResponse.json();

  // Set session cookie (implement your own session logic)
  const cookieStore = await cookies();
  cookieStore.set('session', JSON.stringify({
    user: {
      id: user.sub,
      name: user.name,
      email: user.email
    },
    accessToken: tokens.access_token
  }), {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'lax',
    maxAge: tokens.expires_in
  });

  return NextResponse.redirect(new URL('/dashboard', request.url));
}

Error Handling

Handle errors that may occur during the OAuth flow:

ErrorDescriptionResolution
invalid_requestMissing or invalid parametersCheck all required parameters are present and valid
invalid_clientClient authentication failedVerify client_id and client_secret
invalid_grantAuthorization code is invalid or expiredRequest a new authorization code
unauthorized_clientClient not authorized for this grant typeCheck app configuration in dashboard
access_deniedUser denied the authorization requestHandle gracefully, offer alternative login
invalid_scopeRequested scope is invalid or unknownUse only supported scopes: openid, profile, email

Best Practices

  • Always use HTTPS - All redirect URIs must use HTTPS in production
  • Validate the state parameter - Prevents CSRF attacks
  • Store tokens securely - Use httpOnly cookies or secure server-side storage
  • Request minimal scopes - Only request the data you actually need
  • Handle token expiration - Implement refresh token logic
  • Use PKCE for public clients - Required for SPAs and mobile apps

Need Help?

If you run into issues implementing "Sign in with SoapBox", check our API Reference or reach out to our developer support team.