Skip to main content
PUT
/
admin
/
users
/
{id}
curl -X PUT "http://localhost:8080/admin/users/123e4567-e89b-12d3-a456-426614174000" \
  -H "Authorization: Bearer service_role_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "updated@example.com",
    "phone": "+1234567890",
    "user_metadata": {
      "first_name": "John",
      "last_name": "Doe Updated",
      "company": "Strike Corp"
    },
    "app_metadata": {
      "role": "premium_user",
      "subscription_tier": "pro"
    }
  }'
{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "aud": "authenticated",
  "role": "authenticated",
  "email": "updated@example.com",
  "phone": "+1234567890",
  "email_confirmed_at": "2023-01-01T00:00:00Z",
  "phone_confirmed_at": "2023-01-01T00:00:00Z",
  "last_sign_in_at": "2023-01-01T00:00:00Z",
  "app_metadata": {
    "provider": "email",
    "providers": ["email"],
    "role": "premium_user",
    "subscription_tier": "pro"
  },
  "user_metadata": {
    "first_name": "John",
    "last_name": "Doe Updated",
    "company": "Strike Corp"
  },
  "created_at": "2023-01-01T00:00:00Z",
  "updated_at": "2023-01-02T00:00:00Z",
  "banned_until": null
}
Update user information, metadata, and settings. This endpoint requires service role authentication and allows administrators to modify user accounts programmatically.
This endpoint requires service role authentication. Only use the service role key on your backend servers, never in client-side code.
curl -X PUT "http://localhost:8080/admin/users/123e4567-e89b-12d3-a456-426614174000" \
  -H "Authorization: Bearer service_role_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "updated@example.com",
    "phone": "+1234567890",
    "user_metadata": {
      "first_name": "John",
      "last_name": "Doe Updated",
      "company": "Strike Corp"
    },
    "app_metadata": {
      "role": "premium_user",
      "subscription_tier": "pro"
    }
  }'

Path Parameters

id
string
required
The unique identifier (UUID) of the user to update.

Request Body

email
string
New email address for the user. Must be unique and valid.
phone
string
New phone number in E.164 format (e.g., +1234567890).
password
string
New password for the user. Must meet security requirements.
email_confirmed_at
string
ISO 8601 timestamp when email was confirmed. Set to null to mark as unconfirmed.
phone_confirmed_at
string
ISO 8601 timestamp when phone was confirmed. Set to null to mark as unconfirmed.
user_metadata
object
User-controlled metadata (profile information, preferences, etc.).
app_metadata
object
Application-controlled metadata (roles, permissions, subscription info, etc.).
ban_duration
string
Duration to ban the user (e.g., “24h”, “7d”, “permanent”). Set to null to unban.

Response

id
string
Unique identifier for the user
aud
string
Audience claim, typically “authenticated”
role
string
User’s role in the system
email
string
User’s email address
phone
string
User’s phone number
email_confirmed_at
string
ISO 8601 timestamp when email was confirmed
phone_confirmed_at
string
ISO 8601 timestamp when phone was confirmed
last_sign_in_at
string
ISO 8601 timestamp of last sign in
app_metadata
object
Application-controlled metadata
user_metadata
object
User-controlled metadata
created_at
string
ISO 8601 timestamp when user was created
updated_at
string
ISO 8601 timestamp when user was last updated
banned_until
string
ISO 8601 timestamp until when user is banned (if applicable)
{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "aud": "authenticated",
  "role": "authenticated",
  "email": "updated@example.com",
  "phone": "+1234567890",
  "email_confirmed_at": "2023-01-01T00:00:00Z",
  "phone_confirmed_at": "2023-01-01T00:00:00Z",
  "last_sign_in_at": "2023-01-01T00:00:00Z",
  "app_metadata": {
    "provider": "email",
    "providers": ["email"],
    "role": "premium_user",
    "subscription_tier": "pro"
  },
  "user_metadata": {
    "first_name": "John",
    "last_name": "Doe Updated",
    "company": "Strike Corp"
  },
  "created_at": "2023-01-01T00:00:00Z",
  "updated_at": "2023-01-02T00:00:00Z",
  "banned_until": null
}

Error Responses

{
  "code": 400,
  "msg": "Invalid email format",
  "details": "Please provide a valid email address"
}

Update Operations

Update Profile Information

{
  "user_metadata": {
    "first_name": "Jane",
    "last_name": "Smith",
    "avatar_url": "https://example.com/avatar.jpg",
    "bio": "Software engineer at Strike",
    "location": "San Francisco, CA"
  }
}

Update Contact Information

{
  "email": "newemail@example.com",
  "phone": "+1987654321",
  "email_confirmed_at": null,
  "phone_confirmed_at": null
}

Update App Metadata (Roles & Permissions)

{
  "app_metadata": {
    "role": "admin",
    "permissions": ["read", "write", "delete"],
    "subscription_tier": "enterprise",
    "team_id": "team_123",
    "last_payment_date": "2023-01-01T00:00:00Z"
  }
}

Ban/Unban User

{
  "ban_duration": "7d"
}
{
  "ban_duration": null
}

Reset Password

{
  "password": "new_secure_password_123!",
  "email_confirmed_at": "2023-01-01T00:00:00Z"
}

Implementation Examples

React Admin User Update Form

import { useState, useEffect } from 'react';

function AdminUserUpdateForm({ userId, onUserUpdated }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState('');
  const [formData, setFormData] = useState({
    email: '',
    phone: '',
    user_metadata: {},
    app_metadata: {}
  });

  useEffect(() => {
    fetchUser();
  }, [userId]);

  const fetchUser = async () => {
    try {
      const response = await fetch(`/api/admin/users/${userId}`, {
        headers: {
          'Authorization': `Bearer ${process.env.REACT_APP_SERVICE_ROLE_KEY}`,
        },
      });

      if (!response.ok) throw new Error('Failed to fetch user');

      const userData = await response.json();
      setUser(userData);
      setFormData({
        email: userData.email || '',
        phone: userData.phone || '',
        user_metadata: userData.user_metadata || {},
        app_metadata: userData.app_metadata || {}
      });
    } catch (err) {
      setError(err.message);
    }
  };

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

    try {
      const response = await fetch(`/api/admin/users/${userId}`, {
        method: 'PUT',
        headers: {
          'Authorization': `Bearer ${process.env.REACT_APP_SERVICE_ROLE_KEY}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(formData),
      });

      if (!response.ok) {
        const errorData = await response.json();
        throw new Error(errorData.msg || 'Failed to update user');
      }

      const updatedUser = await response.json();
      setUser(updatedUser);
      onUserUpdated?.(updatedUser);
      
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };

  const updateUserMetadata = (key, value) => {
    setFormData(prev => ({
      ...prev,
      user_metadata: {
        ...prev.user_metadata,
        [key]: value
      }
    }));
  };

  const updateAppMetadata = (key, value) => {
    setFormData(prev => ({
      ...prev,
      app_metadata: {
        ...prev.app_metadata,
        [key]: value
      }
    }));
  };

  if (!user) return <div>Loading user...</div>;

  return (
    <form onSubmit={handleSubmit} className="admin-user-form">
      <h2>Update User: {user.email}</h2>
      
      {error && (
        <div className="error-message">
          {error}
        </div>
      )}

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

        <div className="form-group">
          <label htmlFor="phone">Phone</label>
          <input
            type="tel"
            id="phone"
            value={formData.phone}
            onChange={(e) => setFormData(prev => ({
              ...prev,
              phone: e.target.value
            }))}
            placeholder="+1234567890"
          />
        </div>
      </div>

      <div className="form-section">
        <h3>Profile Information</h3>
        
        <div className="form-group">
          <label htmlFor="firstName">First Name</label>
          <input
            type="text"
            id="firstName"
            value={formData.user_metadata.first_name || ''}
            onChange={(e) => updateUserMetadata('first_name', e.target.value)}
          />
        </div>

        <div className="form-group">
          <label htmlFor="lastName">Last Name</label>
          <input
            type="text"
            id="lastName"
            value={formData.user_metadata.last_name || ''}
            onChange={(e) => updateUserMetadata('last_name', e.target.value)}
          />
        </div>

        <div className="form-group">
          <label htmlFor="company">Company</label>
          <input
            type="text"
            id="company"
            value={formData.user_metadata.company || ''}
            onChange={(e) => updateUserMetadata('company', e.target.value)}
          />
        </div>
      </div>

      <div className="form-section">
        <h3>App Settings</h3>
        
        <div className="form-group">
          <label htmlFor="role">Role</label>
          <select
            id="role"
            value={formData.app_metadata.role || 'user'}
            onChange={(e) => updateAppMetadata('role', e.target.value)}
          >
            <option value="user">User</option>
            <option value="premium_user">Premium User</option>
            <option value="admin">Admin</option>
            <option value="super_admin">Super Admin</option>
          </select>
        </div>

        <div className="form-group">
          <label htmlFor="subscriptionTier">Subscription Tier</label>
          <select
            id="subscriptionTier"
            value={formData.app_metadata.subscription_tier || 'free'}
            onChange={(e) => updateAppMetadata('subscription_tier', e.target.value)}
          >
            <option value="free">Free</option>
            <option value="pro">Pro</option>
            <option value="enterprise">Enterprise</option>
          </select>
        </div>
      </div>

      <div className="form-actions">
        <button type="submit" disabled={loading}>
          {loading ? 'Updating...' : 'Update User'}
        </button>
        
        <button type="button" onClick={fetchUser}>
          Reset Changes
        </button>
      </div>
    </form>
  );
}

export default AdminUserUpdateForm;

Node.js Backend Handler

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

const router = express.Router();

// Middleware to verify service role
const verifyServiceRole = (req, res, next) => {
  const authHeader = req.headers.authorization;
  const token = authHeader?.split(' ')[1];

  if (!token || token !== process.env.SERVICE_ROLE_KEY) {
    return res.status(401).json({
      code: 401,
      msg: 'Invalid service role key',
      details: 'Please provide a valid service role key'
    });
  }

  next();
};

router.put('/admin/users/:id', [
  verifyServiceRole,
  param('id').isUUID().withMessage('Invalid user ID format'),
  body('email').optional().isEmail().normalizeEmail(),
  body('phone').optional().matches(/^\+[1-9]\d{1,14}$/),
  body('password').optional().isLength({ min: 8 }),
  body('user_metadata').optional().isObject(),
  body('app_metadata').optional().isObject()
], async (req, res) => {
  try {
    // Check validation errors
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({
        code: 400,
        msg: 'Invalid request data',
        details: errors.array()
      });
    }

    const { id } = req.params;
    const updateData = req.body;

    // Call Strike Auth Service
    const response = await fetch(`${process.env.AUTH_SERVICE_URL}/admin/users/${id}`, {
      method: 'PUT',
      headers: {
        'Authorization': `Bearer ${process.env.SERVICE_ROLE_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(updateData),
    });

    const result = await response.json();

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

    // Log user update
    console.log(`User updated: ${id}, fields: ${Object.keys(updateData).join(', ')}`);

    res.json(result);

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

module.exports = router;

Bulk User Update

async function bulkUpdateUsers(updates) {
  const results = [];
  
  for (const update of updates) {
    try {
      const response = await fetch(`/api/admin/users/${update.id}`, {
        method: 'PUT',
        headers: {
          'Authorization': `Bearer ${process.env.SERVICE_ROLE_KEY}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(update.data),
      });

      const result = await response.json();
      
      if (response.ok) {
        results.push({ id: update.id, success: true, data: result });
      } else {
        results.push({ id: update.id, success: false, error: result.msg });
      }
    } catch (error) {
      results.push({ id: update.id, success: false, error: error.message });
    }
  }

  return results;
}

// Usage
const updates = [
  {
    id: 'user1-id',
    data: { app_metadata: { subscription_tier: 'pro' } }
  },
  {
    id: 'user2-id',
    data: { app_metadata: { subscription_tier: 'enterprise' } }
  }
];

const results = await bulkUpdateUsers(updates);
console.log('Bulk update results:', results);

Security Considerations

  • Service Role Protection: Never expose service role keys in client-side code
  • Input Validation: Validate all input data before processing
  • Audit Logging: Log all user modifications for compliance
  • Rate Limiting: Implement rate limits for admin operations
  • Permission Checks: Verify admin permissions before allowing updates

Best Practices

  • Validate email uniqueness before updating
  • Ensure phone numbers are in E.164 format
  • Validate password strength requirements
  • Sanitize metadata to prevent injection attacks
  • Use user_metadata for user-controlled data
  • Use app_metadata for application-controlled data
  • Implement schema validation for metadata
  • Consider metadata size limits
  • Provide clear feedback on update success/failure
  • Show validation errors clearly
  • Implement optimistic updates where appropriate
  • Allow partial updates without requiring all fields

Testing

Unit Tests

describe('PUT /admin/users/:id', () => {
  test('should update user with valid data', async () => {
    const updateData = {
      email: 'updated@example.com',
      user_metadata: { first_name: 'Updated' }
    };

    const response = await request(app)
      .put('/admin/users/valid-user-id')
      .set('Authorization', 'Bearer valid_service_role_key')
      .send(updateData)
      .expect(200);

    expect(response.body.email).toBe('updated@example.com');
    expect(response.body.user_metadata.first_name).toBe('Updated');
  });

  test('should reject invalid service role', async () => {
    await request(app)
      .put('/admin/users/valid-user-id')
      .set('Authorization', 'Bearer invalid_key')
      .send({ email: 'test@example.com' })
      .expect(401);
  });

  test('should return 404 for non-existent user', async () => {
    await request(app)
      .put('/admin/users/non-existent-id')
      .set('Authorization', 'Bearer valid_service_role_key')
      .send({ email: 'test@example.com' })
      .expect(404);
  });
});
I