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
- Sign in to the Developer Dashboard
- Create a new application or select an existing one
- Navigate to the OAuth Settings section
- Add your authorized redirect URIs
- Copy your
client_idandclient_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_stringParameters
| Parameter | Required | Description |
|---|---|---|
client_id | Yes | Your application's client ID from the dashboard |
redirect_uri | Yes | URL to redirect back to after authorization. Must match a registered URI. |
response_type | Yes | Must be code for the authorization code flow |
scope | Yes | Space-separated list of scopes (e.g., openid profile email) |
state | Recommended | Random 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_stringImportant
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/callbackResponse
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "dGhpcyBpcyBhIHJlZnJlc2g...",
"scope": "openid profile email",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
}| Field | Description |
|---|---|
access_token | Token to access protected resources |
token_type | Always "Bearer" |
expires_in | Token lifetime in seconds (typically 3600) |
refresh_token | Token to obtain new access tokens |
scope | Granted scopes |
id_token | JWT 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_TOKENResponse
{
"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.
| Scope | Description | Claims Provided |
|---|---|---|
openid | Required for OpenID Connect. Returns an ID token. | sub |
profile | Access to basic profile information | name, given_name, family_name, picture |
email | Access to the user's email address | email, 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:
| Error | Description | Resolution |
|---|---|---|
invalid_request | Missing or invalid parameters | Check all required parameters are present and valid |
invalid_client | Client authentication failed | Verify client_id and client_secret |
invalid_grant | Authorization code is invalid or expired | Request a new authorization code |
unauthorized_client | Client not authorized for this grant type | Check app configuration in dashboard |
access_denied | User denied the authorization request | Handle gracefully, offer alternative login |
invalid_scope | Requested scope is invalid or unknown | Use 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.