Moodle Web Services: Minimum Secure Machine-to-Machine Design
A practical architecture note for using Moodle Web Services securely: minimal protocols, a dedicated service user with least privilege, token handling, and operational checks to avoid privilege escalation.
16 Jul 2026, 17:35 UTC

The problem with direct Moodle integration
Machine-to-machine integrations need to call Moodle without a browser session. The Web Services API exposes core and plugin functionality as external functions, but each call runs with the privileges of the token owner. The useful takeaway is to treat the web service boundary as a user session: limit who the user is, what functions are exposed, and how the token travels.
Requirements for a safe integration
An integration must authenticate without interactive login, call a bounded set of operations, and be auditable. Moodle meets this with external functions declared in db/services.php and managed via Site administration > Plugins > Web services.
Three protocols are supported: REST, XML-RPC and SOAP. REST is the practical choice for modern integrations because it uses standard HTTP and returns JSON by default. Authentication is via personal access tokens generated per user. Each request must include a valid token and the calling user must hold moodle/webservice:use plus any capability required by the specific external function.
Access is enforced at two boundaries. The web service layer validates protocol enablement, token validity and function enablement. Then Moodle's standard capability system validates the user context for the requested operation. This two-stage check is why the service user identity matters as much as the token.
Smallest suitable design
Enable only what is required.
- Protocols: enable REST only. Disable XML-RPC and SOAP in Site administration > Plugins > Web services > Protocols if they are not used.
- Functions: in Site administration > Plugins > Web services > External functions, enable only the specific external functions needed for the integration. Do not enable the whole service.
- Service user: create a dedicated non-human user for the integration. Assign the minimum capabilities required by the chosen functions, and grant moodle/webservice:use. Avoid using an administrator account.
- Token: generate a personal access token for the service user under Site administration > Plugins > Web services > Tokens. Store the token in a secret manager, not in code or logs.
- Network: restrict the Moodle web service endpoint to HTTPS and, where possible, restrict source IP addresses at the web server or WAF layer. Moodle does not enforce transport security at the application layer.
Example request shape for a read-only user lookup. Run from the integration host with network access to Moodle. Requires an admin-created token and a service user with appropriate capabilities.
curl -G "https://{{moodle_url}}/webservice/rest/server.php" \
--data-urlencode "wstoken={{token}}" \
--data-urlencode "wsfunction=core_user_get_users_by_field" \
--data-urlencode "field=id" \
--data-urlencode "values[0]=2" \
--data-urlencode "moodlewsrestformat=json"
Meaningful placeholders: {{moodle_url}} is the base URL of the Moodle site, {{token}} is the personal access token. Expected check is a JSON response with a wrapper object containing the requested data, or an authentication/access exception if the token or capabilities are invalid. Risk: the token appears in process lists and logs if passed on the command line; use environment variables or a secret store.
Trust and data boundaries
The web service layer is a trust boundary. Inside it, Moodle trusts the token as proof of user identity. Outside it, the client must protect the token in transit and at rest.
Data boundary: external functions run with the privileges of the token owner. A compromised token grants the same access as that user's session within Moodle. Therefore the service user must be minimally privileged and the enabled functions must be read-only where possible.
Capability boundary: each external function should declare required capabilities in db/services.php. For custom plugins, verify that required capabilities are listed and that input parameters are validated. Missing validation can lead to unauthorized data exposure or modification.
Transport boundary: Moodle does not enforce HTTPS. If REST is enabled over HTTP, tokens and payloads are exposed to interception. Enforce TLS at the reverse proxy or web server.
Operational checks
Practical verification without assuming behavior.
- Confirm REST is enabled and other protocols are disabled in Site administration > Plugins > Web services > Protocols.
- Create a test token for a test user and call a core read function such as core_user_get_users_by_field. Verify the response contains only fields the caller is permitted to read.
- Attempt the same call with an invalid token or a user lacking moodle/webservice:use and confirm Moodle returns an authentication or access exception.
- Review db/services.php in any custom plugin to confirm each external function lists required capabilities and input parameter validation.
Limitations: token revocation requires manual deletion in the UI. There is no built-in token rotation or scope limitation beyond capabilities. Moodle major versions may differ in web service defaults and deprecation schedules; behavior should not be assumed identical across releases.
Failure modes
Token leakage grants full user privileges. Mitigate with short-lived operational processes, secret management, and IP restrictions.
Over-privileged service user. If the service user can create courses or enroll users, a compromised token can change data. Keep capabilities to the minimum required.
Unused protocols enabled. XML-RPC or SOAP left enabled increases attack surface and may be used by legacy clients.
Custom external function without capability declaration or input validation. This can bypass Moodle's normal access checks.
When to change the design
Conditions that invalidate the smallest design:
- Need for server-to-server calls without a user context. The current token model is user-bound. Consider a different integration pattern.
- Requirement for fine-grained scopes or OAuth2 flows. Personal access tokens do not provide scopes.
- High throughput or long-running jobs. Web services are synchronous and subject to PHP execution limits. Batch via scheduled tasks may be more suitable.
- Multi-tenant isolation needs. A single service user cannot be safely shared across tenants without additional application-level checks.
Keep the design minimal until one of these conditions is proven by requirements, then re-evaluate the trust boundaries and operational checks.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.