Skip to main content

Overview

This guide walks you through implementing a complete user registration flow using Strike Auth Service. We’ll cover different signup methods, email verification, error handling, and best practices.

Basic Signup Flow

1

User Registration

User provides email/phone and password
2

Account Creation

Service creates user account and sends verification
3

Email/SMS Verification

User clicks verification link or enters OTP
4

Account Activation

Account is activated and user can sign in

Implementation Examples

Frontend Implementation (React)

import { useState } from 'react';

function SignupForm() {
  const [formData, setFormData] = useState({
    email: '',
    password: '',
    firstName: '',
    lastName: ''
  });
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState('');
  const [success, setSuccess] = useState(false);

  const handleSubmit = async (e) => {
    e.preventDefault();
    setLoading(true);
    setError('');

    try {
      const response = await fetch('/api/auth/signup', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          email: formData.email,
          password: formData.password,
          data: {
            first_name: formData.firstName,
            last_name: formData.lastName
          }
        }),
      });

      if (!response.ok) {
        const errorData = await response.json();
        throw new Error(errorData.msg || 'Signup failed');
      }

      const user = await response.json();
      setSuccess(true);
      
      // Redirect to verification page or show success message
      
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };

  if (success) {
    return (
      <div className="success-message">
        <h2>Check your email!</h2>
        <p>We've sent a verification link to {formData.email}</p>
        <p>Please click the link to activate your account.</p>
      </div>
    );
  }

  return (
    <form onSubmit={handleSubmit} className="signup-form">
      <h2>Create Account</h2>
      
      {error && (
        <div className="error-message">
          {error}
        </div>
      )}

      <div className="form-group">
        <label htmlFor="firstName">First Name</label>
        <input
          type="text"
          id="firstName"
          value={formData.firstName}
          onChange={(e) => setFormData({
            ...formData,
            firstName: e.target.value
          })}
          required
        />
      </div>

      <div className="form-group">
        <label htmlFor="lastName">Last Name</label>
        <input
          type="text"
          id="lastName"
          value={formData.lastName}
          onChange={(e) => setFormData({
            ...formData,
            lastName: e.target.value
          })}
          required
        />
      </div>

      <div className="form-group">
        <label htmlFor="email">Email</label>
        <input
          type="email"
          id="email"
          value={formData.email}
          onChange={(e) => setFormData({
            ...formData,
            email: e.target.value
          })}
          required
        />
      </div>

      <div className="form-group">
        <label htmlFor="password">Password</label>
        <input
          type="password"
          id="password"
          value={formData.password}
          onChange={(e) => setFormData({
            ...formData,
            password: e.target.value
          })}
          required
          minLength={8}
        />
        <small>
          Password must be at least 8 characters with uppercase, lowercase, number, and special character.
        </small>
      </div>

      <button type="submit" disabled={loading}>
        {loading ? 'Creating Account...' : 'Sign Up'}
      </button>

      <p>
        Already have an account? <a href="/login">Sign in</a>
      </p>
    </form>
  );
}

export default SignupForm;

Backend API Route (Node.js/Express)

const express = require('express');
const { body, validationResult } = require('express-validator');

const router = express.Router();

// Validation middleware
const signupValidation = [
  body('email').isEmail().normalizeEmail(),
  body('password').isLength({ min: 8 }).matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])/),
  body('data.first_name').trim().isLength({ min: 1 }),
  body('data.last_name').trim().isLength({ min: 1 })
];

router.post('/signup', signupValidation, async (req, res) => {
  try {
    // Check validation errors
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({
        code: 400,
        msg: 'Validation failed',
        details: errors.array()
      });
    }

    // Call Strike Auth Service
    const response = await fetch(`${process.env.AUTH_SERVICE_URL}/signup`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(req.body),
    });

    const data = await response.json();

    if (!response.ok) {
      return res.status(response.status).json(data);
    }

    // Log successful signup
    console.log(`New user registered: ${data.email}`);

    // Return user data (without sensitive information)
    res.status(201).json({
      id: data.id,
      email: data.email,
      email_confirmed_at: data.email_confirmed_at,
      created_at: data.created_at
    });

  } catch (error) {
    console.error('Signup error:', error);
    res.status(500).json({
      code: 500,
      msg: 'Internal server error',
      details: 'Please try again later'
    });
  }
});

module.exports = router;

Email Verification Flow

Verification Email Template

<!DOCTYPE html>
<html>
<head>
  <title>Verify Your Email - Strike</title>
</head>
<body>
  <div style="max-width: 600px; margin: 0 auto; padding: 20px;">
    <h1>Welcome to Strike!</h1>
    
    <p>Hi {{.UserMetaData.first_name}},</p>
    
    <p>Thanks for signing up! Please verify your email address by clicking the button below:</p>
    
    <a href="{{.ConfirmationURL}}" 
       style="background-color: #0D9373; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px; display: inline-block;">
      Verify Email Address
    </a>
    
    <p>Or copy and paste this link into your browser:</p>
    <p>{{.ConfirmationURL}}</p>
    
    <p>This link will expire in 24 hours.</p>
    
    <p>If you didn't create an account, you can safely ignore this email.</p>
    
    <p>Best regards,<br>The Strike Team</p>
  </div>
</body>
</html>

Handling Verification Callback

// Verification callback handler
router.get('/verify', async (req, res) => {
  const { type, token, redirect_to } = req.query;

  try {
    // Verify the token with Strike Auth Service
    const response = await fetch(`${process.env.AUTH_SERVICE_URL}/verify?type=${type}&token=${token}`, {
      method: 'GET',
    });

    if (response.ok) {
      // Successful verification - redirect to app
      const redirectUrl = redirect_to || '/dashboard';
      res.redirect(redirectUrl);
    } else {
      // Verification failed
      res.redirect('/verification-failed');
    }
  } catch (error) {
    console.error('Verification error:', error);
    res.redirect('/verification-failed');
  }
});

// Resend verification email
router.post('/resend-verification', async (req, res) => {
  const { email } = req.body;

  try {
    const response = await fetch(`${process.env.AUTH_SERVICE_URL}/resend`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        type: 'signup',
        email: email
      }),
    });

    if (response.ok) {
      res.json({ message: 'Verification email sent' });
    } else {
      const error = await response.json();
      res.status(response.status).json(error);
    }
  } catch (error) {
    res.status(500).json({ 
      code: 500, 
      msg: 'Failed to resend verification email' 
    });
  }
});

Phone Number Signup

SMS Verification Flow

function PhoneSignupForm() {
  const [step, setStep] = useState('phone'); // 'phone' or 'verify'
  const [phoneNumber, setPhoneNumber] = useState('');
  const [otp, setOtp] = useState('');
  const [password, setPassword] = useState('');

  const handlePhoneSubmit = async (e) => {
    e.preventDefault();
    
    try {
      // Send OTP
      await fetch('/api/auth/otp', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          phone: phoneNumber,
          create_user: true
        })
      });
      
      setStep('verify');
    } catch (error) {
      console.error('Failed to send OTP:', error);
    }
  };

  const handleVerifySubmit = async (e) => {
    e.preventDefault();
    
    try {
      // Verify OTP and create account
      const response = await fetch('/api/auth/verify', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          type: 'sms',
          token: otp,
          phone: phoneNumber,
          password: password
        })
      });
      
      if (response.ok) {
        // Account created and verified
        window.location.href = '/dashboard';
      }
    } catch (error) {
      console.error('Verification failed:', error);
    }
  };

  if (step === 'verify') {
    return (
      <form onSubmit={handleVerifySubmit}>
        <h2>Verify Your Phone</h2>
        <p>Enter the code sent to {phoneNumber}</p>
        
        <input
          type="text"
          placeholder="Enter 6-digit code"
          value={otp}
          onChange={(e) => setOtp(e.target.value)}
          maxLength={6}
          required
        />
        
        <input
          type="password"
          placeholder="Create password"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
          required
        />
        
        <button type="submit">Verify & Create Account</button>
      </form>
    );
  }

  return (
    <form onSubmit={handlePhoneSubmit}>
      <h2>Sign Up with Phone</h2>
      
      <input
        type="tel"
        placeholder="+1 (555) 123-4567"
        value={phoneNumber}
        onChange={(e) => setPhoneNumber(e.target.value)}
        required
      />
      
      <button type="submit">Send Verification Code</button>
    </form>
  );
}

OAuth Signup

Social Login Buttons

function SocialSignup() {
  const handleOAuthSignup = (provider) => {
    const redirectUrl = encodeURIComponent(window.location.origin + '/auth/callback');
    window.location.href = `/api/auth/authorize?provider=${provider}&redirect_to=${redirectUrl}`;
  };

  return (
    <div className="social-signup">
      <h3>Or sign up with</h3>
      
      <button 
        onClick={() => handleOAuthSignup('google')}
        className="oauth-button google"
      >
        <img src="/icons/google.svg" alt="Google" />
        Continue with Google
      </button>
      
      <button 
        onClick={() => handleOAuthSignup('github')}
        className="oauth-button github"
      >
        <img src="/icons/github.svg" alt="GitHub" />
        Continue with GitHub
      </button>
      
      <button 
        onClick={() => handleOAuthSignup('apple')}
        className="oauth-button apple"
      >
        <img src="/icons/apple.svg" alt="Apple" />
        Continue with Apple
      </button>
    </div>
  );
}

OAuth Callback Handler

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

  if (error) {
    return res.redirect('/signup?error=' + encodeURIComponent(error));
  }

  try {
    // Exchange code for tokens
    const response = await fetch(`${process.env.AUTH_SERVICE_URL}/callback?code=${code}&state=${state}`, {
      method: 'GET',
    });

    if (response.ok) {
      // OAuth signup successful
      const data = await response.json();
      
      // Set session cookies
      res.cookie('access_token', data.access_token, {
        httpOnly: true,
        secure: process.env.NODE_ENV === 'production',
        maxAge: data.expires_in * 1000
      });
      
      res.cookie('refresh_token', data.refresh_token, {
        httpOnly: true,
        secure: process.env.NODE_ENV === 'production',
        maxAge: 30 * 24 * 60 * 60 * 1000 // 30 days
      });
      
      res.redirect('/dashboard');
    } else {
      res.redirect('/signup?error=oauth_failed');
    }
  } catch (error) {
    console.error('OAuth callback error:', error);
    res.redirect('/signup?error=oauth_failed');
  }
});

Error Handling

Common Signup Errors

const handleSignupError = (error) => {
  switch (error.code) {
    case 400:
      if (error.msg.includes('email')) {
        return 'Please enter a valid email address';
      }
      if (error.msg.includes('password')) {
        return 'Password must be at least 8 characters with uppercase, lowercase, number, and special character';
      }
      return 'Please check your input and try again';
      
    case 422:
      if (error.msg.includes('already registered')) {
        return 'An account with this email already exists. Try signing in instead.';
      }
      return error.msg;
      
    case 429:
      return 'Too many signup attempts. Please wait a few minutes and try again.';
      
    case 500:
      return 'Something went wrong on our end. Please try again later.';
      
    default:
      return 'Signup failed. Please try again.';
  }
};

User-Friendly Error Messages

function ErrorMessage({ error }) {
  const getErrorMessage = (error) => {
    if (typeof error === 'string') return error;
    
    return handleSignupError(error);
  };

  if (!error) return null;

  return (
    <div className="error-message" role="alert">
      <div className="error-icon">⚠️</div>
      <div className="error-text">
        {getErrorMessage(error)}
      </div>
    </div>
  );
}

Best Practices

  • Provide clear password requirements
  • Show real-time validation feedback
  • Use progressive disclosure for complex forms
  • Offer multiple signup options (email, phone, OAuth)
  • Implement proper loading states
  • Validate input on both client and server
  • Implement rate limiting
  • Use HTTPS for all requests
  • Hash passwords securely
  • Implement CAPTCHA for high-risk scenarios
  • Send verification emails immediately
  • Provide clear instructions
  • Include resend functionality
  • Set appropriate expiration times
  • Handle edge cases gracefully
  • Provide specific, actionable error messages
  • Don’t reveal sensitive information
  • Log errors for debugging
  • Implement retry mechanisms
  • Graceful degradation for network issues

Testing Your Signup Flow

Unit Tests

describe('Signup API', () => {
  test('should create user with valid data', async () => {
    const userData = {
      email: 'test@example.com',
      password: 'SecurePass123!',
      data: {
        first_name: 'Test',
        last_name: 'User'
      }
    };

    const response = await request(app)
      .post('/api/auth/signup')
      .send(userData)
      .expect(201);

    expect(response.body.email).toBe(userData.email);
    expect(response.body.id).toBeDefined();
  });

  test('should reject invalid email', async () => {
    const userData = {
      email: 'invalid-email',
      password: 'SecurePass123!'
    };

    await request(app)
      .post('/api/auth/signup')
      .send(userData)
      .expect(400);
  });
});

Integration Tests

describe('Signup Flow Integration', () => {
  test('complete signup and verification flow', async () => {
    // 1. Sign up user
    const signupResponse = await request(app)
      .post('/api/auth/signup')
      .send({
        email: 'integration@example.com',
        password: 'SecurePass123!'
      });

    expect(signupResponse.status).toBe(201);

    // 2. Verify email (mock verification)
    const verifyResponse = await request(app)
      .get('/api/auth/verify')
      .query({
        type: 'signup',
        token: 'mock-verification-token'
      });

    expect(verifyResponse.status).toBe(302); // Redirect
  });
});

Next Steps

After implementing the signup flow:
I