Skip to content

Commit 7d4862f

Browse files
authored
security: harden SSO API endpoint (#6)
* docs: document hardened API authentication * chore: retrigger PHP lint
1 parent d17008c commit 7d4862f

2 files changed

Lines changed: 53 additions & 19 deletions

File tree

README.md

Lines changed: 52 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,15 @@ The IPS plugin can then use that endpoint as the bridge between the two applicat
2626
├── wp_api.php # WordPress-side API endpoint
2727
├── WordPress SSO.xml # IPS Community Suite plugin definition
2828
├── README.md # Project documentation
29+
├── SECURITY.md # Security policy and reporting guidance
2930
└── .gitignore
3031
```
3132

3233
## Requirements
3334

3435
- WordPress
3536
- IPS / Invision Community installed on the same environment or otherwise able to reach the WordPress endpoint
36-
- PHP 7.4 or newer for the existing implementation
37+
- PHP 7.4 or newer
3738
- access to the WordPress filesystem and IPS plugin administration
3839

3940
For new deployments, prefer a currently supported PHP release and test the integration against the exact WordPress and IPS versions you use.
@@ -44,13 +45,13 @@ For new deployments, prefer a currently supported PHP release and test the integ
4445

4546
Copy `wp_api.php` into the WordPress installation root, next to `wp-load.php`.
4647

47-
Edit this line:
48+
The preferred configuration is to provide the secret through an environment variable:
4849

49-
```php
50-
$apiKey = 'YOUR-API-HERE';
50+
```text
51+
WP_SSO_API_KEY=your-long-random-secret
5152
```
5253

53-
Replace the placeholder with a long random secret and keep it private.
54+
For legacy environments where an environment variable is not practical, the placeholder near the top of `wp_api.php` can still be replaced manually. Never commit the real secret to the repository.
5455

5556
### 2. Configure the shared cookie domain when required
5657

@@ -68,15 +69,15 @@ Do not define `COOKIE_DOMAIN` twice. Use the value that matches your actual depl
6869

6970
Import `WordPress SSO.xml` manually from the IPS plugin administration area.
7071

71-
Open the WP-SSO plugin settings and configure the WordPress API endpoint and the same API secret used in `wp_api.php`.
72+
Open the WP-SSO plugin settings and configure the WordPress API endpoint and the same API secret used by `wp_api.php`.
7273

7374
### 4. Configure login flow
7475

7576
If desired, point the IPS login flow to the WordPress login page so authentication happens through WordPress first.
7677

7778
## Endpoint behavior
7879

79-
The WordPress endpoint currently supports these `type` values:
80+
The WordPress endpoint supports these `type` values:
8081

8182
| Type | Purpose |
8283
| --- | --- |
@@ -87,46 +88,78 @@ The WordPress endpoint currently supports these `type` values:
8788
| `logout` | Return a WordPress logout URL |
8889
| `test` | Return `OK` to confirm connectivity |
8990

90-
All requests require the configured `api_key` query parameter.
91+
### Authentication
9192

92-
Example connectivity request:
93+
New clients should send the API secret in the `X-WP-SSO-Key` header:
94+
95+
```bash
96+
curl -H "X-WP-SSO-Key: YOUR_SECRET" \
97+
"https://example.com/wp_api.php?type=test"
98+
```
99+
100+
Bearer authentication is also accepted:
101+
102+
```bash
103+
curl -H "Authorization: Bearer YOUR_SECRET" \
104+
"https://example.com/wp_api.php?type=test"
105+
```
106+
107+
For compatibility with the existing IPS plugin definition, the legacy `api_key` query parameter remains temporarily supported:
93108

94109
```text
95110
https://example.com/wp_api.php?api_key=YOUR_SECRET&type=test
96111
```
97112

113+
Requests using the query-string secret receive deprecation headers. New integrations should not rely on this form because URLs may be captured by access logs, browser history, reverse proxies, or monitoring tools.
114+
115+
## Security improvements
116+
117+
The maintained endpoint now includes several defensive measures:
118+
119+
- constant-time secret comparison through `hash_equals()`;
120+
- header-based authentication support;
121+
- optional `WP_SSO_API_KEY` environment-variable configuration;
122+
- explicit request-type allowlisting;
123+
- HTTP/HTTPS-only redirect validation;
124+
- JSON content type and `no-store` headers for API responses;
125+
- `X-Content-Type-Options: nosniff` for JSON responses;
126+
- explicit failure when the default placeholder secret has not been replaced;
127+
- safer WordPress bootstrap resolution through `__DIR__`.
128+
129+
The plain-text `OK` response for `type=test` is intentionally retained for compatibility.
130+
98131
## Security notes
99132

100-
This repository contains a legacy integration and should be reviewed before production use.
133+
This repository contains a legacy integration and should still be reviewed before production use.
101134

102135
- Never commit a real API key to GitHub.
103136
- Use HTTPS for both WordPress and IPS.
104137
- Generate a long random API secret.
138+
- Prefer `X-WP-SSO-Key` or Bearer authentication over query-string authentication.
105139
- Restrict access to the endpoint where possible.
106140
- Do not expose debug output or PHP errors publicly.
107-
- Test redirect handling carefully.
108141
- Keep WordPress, IPS, PHP, and all related plugins up to date.
109142

110-
The API secret is currently supplied as a query parameter. Query-string secrets can appear in access logs, browser history, reverse-proxy logs, and monitoring systems. A future version should move authentication to a safer request mechanism such as an HTTP header.
143+
See [SECURITY.md](SECURITY.md) for vulnerability reporting guidance.
111144

112145
## Known limitations
113146

114147
- The current project is not a packaged WordPress plugin; `wp_api.php` is deployed manually.
115-
- The API uses a shared static secret.
116-
- The implementation assumes `wp-load.php` is in the same directory as `wp_api.php`.
148+
- Authentication still relies on a shared static secret.
149+
- The legacy IPS plugin may continue using the deprecated query-string API key until its request layer is modernized.
117150
- Compatibility with modern WordPress / IPS releases has not been continuously verified in this repository.
118-
- There is no automated test suite yet.
151+
- There is no integration test suite yet; current automation performs PHP syntax validation.
119152

120153
## Roadmap
121154

122155
Potential modernization work includes:
123156

124157
- convert the WordPress side into a standard WordPress plugin;
125-
- replace query-string API authentication with an HTTP header;
126-
- add stricter request validation and JSON response headers;
127-
- add automated PHP linting and security checks;
158+
- update the IPS integration to use header-based authentication exclusively;
159+
- add automated endpoint and authentication tests;
128160
- document supported WordPress, IPS, and PHP versions;
129-
- add release packaging and migration documentation.
161+
- add release packaging and migration documentation;
162+
- remove query-string API-key compatibility in a future breaking release.
130163

131164
## Contributing
132165

wp_api.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* Prefer setting the API key through the WP_SSO_API_KEY environment variable.
77
* The query-string api_key parameter remains temporarily supported for legacy
88
* IPS integrations, but new clients should send X-WP-SSO-Key instead.
9+
* Legacy query-string authentication is retained for backward compatibility only.
910
*/
1011

1112
$apiKey = getenv('WP_SSO_API_KEY');

0 commit comments

Comments
 (0)