# Authentication and Authorization

### Securing Your RPC Handlers with Role-Based Access Control

[← Back to Understanding XRPL(d) RPC Architecture](/core-dev-bootcamp/module06.md)

***

## Introduction

Security is paramount when building RPC handlers that interact with the XRP Ledger. Rippled implements a comprehensive **role-based access control (RBAC) system** to ensure that only authorized clients can execute sensitive operations.

In this section, you'll learn how to properly configure permissions, implement role checks, manage resource limits, and protect your custom handlers from unauthorized access.

***

## The Role Hierarchy

Rippled defines five distinct permission levels:

```
FORBID < GUEST < USER < IDENTIFIED < ADMIN
```

### Role Definitions

| Role           | Description                   | Typical Use Case                         |
| -------------- | ----------------------------- | ---------------------------------------- |
| **FORBID**     | Blacklisted client            | Blocked due to abuse                     |
| **GUEST**      | Unauthenticated public access | Public API endpoints, read-only queries  |
| **USER**       | Authenticated client          | Standard API operations, account queries |
| **IDENTIFIED** | Trusted gateway or service    | Transaction submission, privileged reads |
| **ADMIN**      | Full administrative access    | Node management, dangerous operations    |

**Source Location**: `src/xrpld/core/Config.h`

***

## Role Determination

Roles are assigned based on the **client's IP address and connection type**:

### IP-Based Assignment

```cpp
// src/xrpld/core/Config.cpp
Role getRoleFromConnection(
    boost::asio::ip::address const& remoteIP,
    Port const& port)
{
    // Admin IPs have full access
    if (config_.ADMIN.contains(remoteIP))
        return Role::ADMIN;

    // Secure gateway IPs are identified
    if (config_.SECURE_GATEWAY.contains(remoteIP))
        return Role::IDENTIFIED;

    // Check if port requires admin access
    if (port.admin_nets && port.admin_nets->contains(remoteIP))
        return Role::ADMIN;

    // Default to USER for authenticated connections
    return Role::USER;
}
```

### Configuration

**File**: `rippled.cfg`

```ini
# Admin-only access from localhost
[rpc_admin]
admin = 127.0.0.1, ::1

# Trusted gateway access
[secure_gateway]
ip = 192.168.1.100

# Port configuration
[port_rpc_admin_local]
port = 5005
ip = 127.0.0.1
admin = 127.0.0.1
protocol = http

[port_rpc_public]
port = 5006
ip = 0.0.0.0
protocol = http
```

***

## Assigning Roles to Handlers

When registering a handler, specify the **minimum required role**:

### Example Registrations

```cpp
// Public read-only command (available to everyone)
{
    "server_info",
    {
        &doServerInfo,
        Role::GUEST,  // Lowest permission
        RPC::NO_CONDITION
    }
}

// Standard query (requires authentication)
{
    "account_info",
    {
        &doAccountInfo,
        Role::USER,  // Moderate permission
        RPC::NEEDS_CURRENT_LEDGER
    }
}

// Transaction submission (requires trust)
{
    "submit",
    {
        &doSubmit,
        Role::IDENTIFIED,  // Higher permission
        RPC::NEEDS_NETWORK_CONNECTION
    }
}

// Administrative command (full access only)
{
    "stop",
    {
        &doStop,
        Role::ADMIN,  // Maximum permission
        RPC::NO_CONDITION
    }
}
```

***

## Permission Enforcement

The RPC dispatcher automatically enforces role requirements **before** invoking handlers:

### Automatic Check

```cpp
// src/xrpld/rpc/detail/Handler.cpp
if (context.role < handlerInfo.role) {
    return rpcError(rpcNO_PERMISSION,
        "You don't have permission for this command");
}
```

### Manual Check (Inside Handler)

For fine-grained control:

```cpp
Json::Value doSensitiveOperation(RPC::JsonContext& context)
{
    // Check if caller has admin privileges
    if (context.role < Role::ADMIN) {
        return rpcError(rpcNO_PERMISSION,
            "This operation requires admin access");
    }

    // Additional checks
    if (context.role < Role::IDENTIFIED &&
        context.params.isMember("dangerous_option"))
    {
        return rpcError(rpcNO_PERMISSION,
            "Only identified users can use this option");
    }

    // Proceed with operation
    // ...
}
```

***

## Resource Management

Rippled tracks API usage to prevent **denial-of-service attacks**:

### Resource Charging

```cpp
// Each request consumes resources
context.consumer.charge(Resource::feeReferenceRPC);

// High-cost operations charge more
if (isExpensiveQuery) {
    context.consumer.charge(Resource::feeHighBurdenRPC);
}
```

### Resource Limits

```cpp
// Check if client has exceeded limits
if (!context.consumer.isUnlimited() &&
    context.consumer.balance() <= 0)
{
    return rpcError(rpcSLOW_DOWN,
        "You are making requests too frequently");
}
```

### Unlimited Resources

Admin connections have unlimited resources:

```cpp
bool isUnlimited() const
{
    return role_ >= Role::ADMIN;
}
```

***

## IP Whitelisting and Blacklisting

### Whitelisting Admin IPs

```ini
# rippled.cfg
[rpc_admin]
admin = 127.0.0.1
admin = 192.168.1.50
admin = ::1
```

### Blacklisting Abusive Clients

Rippled uses a **"Gossip" mechanism** to share blacklisted IPs across the network:

```cpp
// Mark a client as abusive
context.netOps.reportAbuse(remoteIP);

// Check if IP is blacklisted
if (context.netOps.isBlacklisted(remoteIP)) {
    return rpcError(rpcFORBIDDEN, "Access denied");
}
```

***

## Secure Gateway Mode

For production deployments, use **secure gateway** configuration:

### Architecture

```
Client → Reverse Proxy (nginx) → Rippled
         [IP: 192.168.1.100]      [Trusted]
```

### Configuration

```ini
[secure_gateway]
ip = 192.168.1.100

[port_rpc]
port = 5005
ip = 127.0.0.1
protocol = http
```

**Benefits**:

* Rippled only accepts connections from the proxy
* Proxy handles TLS termination
* Proxy performs initial authentication
* Reduces attack surface

***

## Password Authentication (WebSocket)

WebSocket connections support **optional password authentication**:

### Configuration

```ini
[rpc_startup]
{ "command": "log_level", "severity": "warning" }

[port_ws_admin_local]
port = 6006
ip = 127.0.0.1
admin = 127.0.0.1
protocol = ws
admin_user = myuser
admin_password = mypassword
```

### Client Authentication

```javascript
const ws = new WebSocket('ws://localhost:6006');

ws.send(JSON.stringify({
    command: 'login',
    user: 'myuser',
    password: 'mypassword'
}));

// After successful login, role is elevated to ADMIN
```

***

## Example: Multi-Level Permission Handler

Let's build a handler with different behavior based on role:

```cpp
Json::Value doAccountStats(RPC::JsonContext& context)
{
    // Basic validation
    if (!context.params.isMember(jss::account)) {
        return rpcError(rpcINVALID_PARAMS, "Missing 'account' field");
    }

    auto const account = parseBase58<AccountID>(
        context.params[jss::account].asString()
    );

    if (!account) {
        return rpcError(rpcACT_MALFORMED);
    }

    // Get ledger
    std::shared_ptr<ReadView const> ledger;
    auto const result = RPC::lookupLedger(ledger, context);
    if (!ledger) return result;

    // Read account
    auto const sleAccount = ledger->read(keylet::account(*account));
    if (!sleAccount) {
        return rpcError(rpcACT_NOT_FOUND);
    }

    // Build base response (available to all roles)
    Json::Value response;
    response[jss::account] = to_string(*account);
    response["balance"] = to_string(sleAccount->getFieldAmount(sfBalance));

    // Add details for USER and above
    if (context.role >= Role::USER) {
        response["sequence"] = sleAccount->getFieldU32(sfSequence);
        response["owner_count"] = sleAccount->getFieldU32(sfOwnerCount);
    }

    // Add sensitive info for IDENTIFIED and above
    if (context.role >= Role::IDENTIFIED) {
        response["flags"] = sleAccount->getFieldU32(sfFlags);
        response["previous_txn_id"] = to_string(
            sleAccount->getFieldH256(sfPreviousTxnID)
        );
    }

    // Add administrative data for ADMIN only
    if (context.role >= Role::ADMIN) {
        response["ledger_entry_type"] = "AccountRoot";
        response["index"] = to_string(keylet::account(*account).key);
    }

    return response;
}
```

**Registration**:

```cpp
{
    "account_stats",
    {
        &doAccountStats,
        Role::GUEST,  // Base access for everyone
        RPC::NEEDS_CURRENT_LEDGER
    }
}
```

**Behavior**:

* **GUEST**: Gets only account and balance
* **USER**: Gets sequence and owner count
* **IDENTIFIED**: Gets flags and previous transaction ID
* **ADMIN**: Gets full administrative details

***

## Best Practices

### ✅ DO

* **Always validate roles** before sensitive operations
* **Use the minimum required role** for each handler
* **Charge resources appropriately** for expensive queries
* **Log security events** for audit trails
* **Test with different roles** during development

### ❌ DON'T

* **Don't hardcode IP addresses** in handler code
* **Don't expose admin functions** to lower roles
* **Don't skip resource charging** for expensive operations
* **Don't leak sensitive information** in error messages
* **Don't trust client-provided role information**

***

## Security Checklist

Before deploying a custom handler:

* [ ] Minimum role correctly assigned in handler table
* [ ] Input validation prevents injection attacks
* [ ] Resource charging implemented for expensive operations
* [ ] Sensitive data not exposed to unauthorized roles
* [ ] Error messages don't leak system information
* [ ] Tested with GUEST, USER, and ADMIN roles
* [ ] Logs security-relevant events
* [ ] Follows principle of least privilege

***

### Conclusion

Rippled's authentication and authorization system provides robust protection for the RPC interface through a well-designed role hierarchy. By combining IP-based role assignment, automatic permission enforcement in the dispatcher, resource charging for expensive operations, and fine-grained access control, the system prevents unauthorized access while enabling legitimate use cases. Understanding these security patterns is essential for building handlers that are both functional and secure, and for deploying nodes that safely expose APIs to different client types.

***


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.xrpl-commons.org/core-dev-bootcamp/module06/authentication-authorization.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
