DEV Community

Daniel Ioni
Daniel Ioni

Posted on

πŸ” MyZubster: Complete Authentication System & Frontend Integration

How I built a full-stack authentication system with JWT, React, and Monero paymentsβ€”all on a VPS.
🌱 The Journey Continues

MyZubster is an open-source decentralized marketplace that uses Monero (XMR) for private payments. After deploying the core infrastructure (Gateway, Marketplace, Security Bot), I focused on implementing a complete authentication system and integrating it with the React frontend.

This post covers:

JWT authentication implementation

User registration and login system

React frontend integration

Production deployment with Nginx and HTTPS
Enter fullscreen mode Exit fullscreen mode

πŸ—ΊοΈ What We Built
Feature Technology Status
JWT Authentication Node.js + jsonwebtoken βœ… Complete
User Registration bcryptjs + MongoDB βœ… Complete
User Login JWT + bcrypt βœ… Complete
Protected Routes JWT middleware βœ… Complete
React Frontend React + Axios βœ… Complete
HTTPS Let's Encrypt + Nginx βœ… Complete
πŸ”§ Step 1: Backend Authentication
1.1 User Model (MongoDB)
javascript

// models/User.js
const UserSchema = new mongoose.Schema({
username: {
type: String,
required: true,
unique: true,
trim: true,
minlength: 3,
maxlength: 30
},
email: {
type: String,
required: true,
unique: true,
trim: true,
lowercase: true
},
password: {
type: String,
required: true,
minlength: 6
},
role: {
type: String,
enum: ['user', 'admin', 'moderator'],
default: 'user'
},
isActive: {
type: Boolean,
default: true
}
}, { timestamps: true });

1.2 Authentication Routes
javascript

// routes/auth.js
router.post('/register', async (req, res) => {
const { username, email, password, name } = req.body;

// Check if user exists
const existingUser = await User.findOne({ 
    $or: [{ email }, { username }] 
});
if (existingUser) {
    return res.status(409).json({ 
        success: false, 
        error: 'Username or email already in use' 
    });
}

// Hash password
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(password, salt);

// Create user
const user = new User({
    username,
    email,
    password: hashedPassword,
    name: name || username
});

await user.save();

// Generate JWT
const token = jwt.sign(
    { id: user._id, username: user.username, role: user.role },
    process.env.JWT_SECRET,
    { expiresIn: '7d' }
);

res.status(201).json({
    success: true,
    token,
    user: {
        id: user._id,
        username: user.username,
        email: user.email,
        name: user.name,
        role: user.role
    }
});
Enter fullscreen mode Exit fullscreen mode

});

router.post('/login', async (req, res) => {
const { email, password } = req.body;

const user = await User.findOne({ email });
if (!user) {
    return res.status(401).json({ 
        success: false, 
        error: 'Invalid credentials' 
    });
}

const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) {
    return res.status(401).json({ 
        success: false, 
        error: 'Invalid credentials' 
    });
}

const token = jwt.sign(
    { id: user._id, username: user.username, role: user.role },
    process.env.JWT_SECRET,
    { expiresIn: '7d' }
);

res.json({
    success: true,
    token,
    user: {
        id: user._id,
        username: user.username,
        email: user.email,
        name: user.name,
        role: user.role
    }
});
Enter fullscreen mode Exit fullscreen mode

});

1.3 JWT Middleware
javascript

// server.js
const authenticate = (req, res, next) => {
const publicRoutes = [
'/api/health',
'/api/auth/login',
'/api/auth/register',
'/api/auth/verify'
];

if (publicRoutes.includes(req.path)) {
    return next();
}

const authHeader = req.headers.authorization;
if (!authHeader) {
    return res.status(401).json({ error: 'Token required' });
}

const token = authHeader.split(' ')[1];
try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
} catch {
    return res.status(401).json({ error: 'Invalid or expired token' });
}
Enter fullscreen mode Exit fullscreen mode

};

// Apply to protected routes
app.use('/api', authenticate);

🎨 Step 2: Frontend Integration
2.1 API Service
javascript

// services/api.js
import axios from 'axios';

const API_URL = 'https://myzubster.com/api';

const api = axios.create({
baseURL: API_URL,
headers: { 'Content-Type': 'application/json' }
});

api.interceptors.request.use((config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = Bearer ${token};
}
return config;
});

export default api;

2.2 Auth Service
javascript

// services/auth.js
import api from './api';

export const register = async (userData) => {
const response = await api.post('/auth/register', userData);
return response.data;
};

export const login = async (credentials) => {
const response = await api.post('/auth/login', credentials);
return response.data;
};

export const logout = () => {
localStorage.removeItem('token');
localStorage.removeItem('user');
};

export const getCurrentUser = () => {
const user = localStorage.getItem('user');
return user ? JSON.parse(user) : null;
};

2.3 Auth Component (React)
javascript

// components/Auth.jsx
const Auth = ({ onLogin }) => {
const [isLogin, setIsLogin] = useState(true);
const [formData, setFormData] = useState({
username: '',
email: '',
password: '',
name: ''
});
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);

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

    try {
        let response;
        if (isLogin) {
            response = await login({
                email: formData.email,
                password: formData.password
            });
        } else {
            response = await register(formData);
        }

        if (response.success) {
            localStorage.setItem('token', response.token);
            localStorage.setItem('user', JSON.stringify(response.user));
            onLogin(response.user);
        }
    } catch (err) {
        setError(err.response?.data?.error || 'Authentication failed');
    } finally {
        setLoading(false);
    }
};

return (
    <div className="auth-container">
        <h2>{isLogin ? 'Login' : 'Register'}</h2>
        {error && <div className="error">{error}</div>}
        <form onSubmit={handleSubmit}>
            {!isLogin && (
                <>
                    <input
                        type="text"
                        name="username"
                        placeholder="Username"
                        value={formData.username}
                        onChange={handleChange}
                        required
                    />
                    <input
                        type="text"
                        name="name"
                        placeholder="Full name"
                        value={formData.name}
                        onChange={handleChange}
                    />
                </>
            )}
            <input
                type="email"
                name="email"
                placeholder="Email"
                value={formData.email}
                onChange={handleChange}
                required
            />
            <input
                type="password"
                name="password"
                placeholder="Password"
                value={formData.password}
                onChange={handleChange}
                required
            />
            <button type="submit" disabled={loading}>
                {loading ? 'Loading...' : (isLogin ? 'Login' : 'Register')}
            </button>
        </form>
        <p>
            {isLogin ? "Don't have an account?" : "Already have an account?"}
            <button onClick={() => setIsLogin(!isLogin)}>
                {isLogin ? 'Register' : 'Login'}
            </button>
        </p>
    </div>
);
Enter fullscreen mode Exit fullscreen mode

};

🌐 Step 3: Production Deployment
3.1 Nginx Configuration (HTTPS)
nginx

server {
listen 80;
server_name myzubster.com www.myzubster.com;
return 301 https://$server_name$request_uri;
}

server {
listen 443 ssl http2;
server_name myzubster.com www.myzubster.com;

ssl_certificate /etc/letsencrypt/live/myzubster.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/myzubster.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

root /var/www/myzubster-frontend;
index index.html;

# Security Headers
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';" always;

location / {
    try_files $uri $uri/ /index.html;
}

location /api/ {
    proxy_pass http://localhost:3002/api/;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection 'upgrade';
    proxy_set_header Host $host;
    proxy_cache_bypass $http_upgrade;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}
Enter fullscreen mode Exit fullscreen mode

}

3.2 Deploy Commands
bash

Build frontend

cd ~/myzubster-frontend
npm run build

Deploy to Nginx

sudo cp -r dist/* /var/www/myzubster-frontend/
sudo chown -R www-data:www-data /var/www/myzubster-frontend/

Restart services

sudo systemctl reload nginx

Start gateway

cd ~/MyZubsterGateway
export PORT=3002
nohup node server.js > /var/log/gateway.log 2>&1 &

βœ… Results

After completing the deployment, the system is fully functional:
Component Status URL
Frontend βœ… Live https://myzubster.com
API Gateway βœ… Live https://myzubster.com/api
Registration βœ… Working POST /api/auth/register
Login βœ… Working POST /api/auth/login
Protected Routes βœ… Working JWT middleware
HTTPS βœ… Active Let's Encrypt
Security Headers Verified
text

X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Strict-Transport-Security: max-age=31536000; includeSubDomains
Content-Security-Policy: default-src 'self'...

πŸ’‘ Lessons Learned

JWT is simple yet powerful – Stateless authentication scales well.

Rate limiting is essential – Protects against brute-force attacks.

Environment variables are critical – Keep secrets out of code.

HTTPS is non-negotiable – Let's Encrypt makes it easy.

React + Axios – Clean API integration with interceptors.

Nginx is a great reverse proxy – Handles SSL and API routing efficiently.

Security headers – CSP and HSTS protect against common attacks.
Enter fullscreen mode Exit fullscreen mode

πŸ“š Repository & Documentation

All code is available on GitHub:
Repository Description Stars
MyZubsterGateway Monero payment engine ⭐ 3
myzubster-frontend React frontend ⭐ 0
MyZubster-Marketplace Backend API ⭐ 1
🀝 Contribute

If you're interested in blockchain, privacy-first payments, or decentralized markets, contribute to the repositories on GitHub or open an issue to discuss new features.
🌐 Connect with Me

Blog: DEV.to - Daniel Ioni

X (Twitter): @myzubster

LinkedIn: Daniel Ioni

GitHub: DanielIoni-creator

TikTok: @h4x0r_23
Enter fullscreen mode Exit fullscreen mode

Built with ❀️ for Monero, open source, and the global community.

Top comments (0)