A dual-interface API server for WinCC OA Manager featuring both GraphQL and REST APIs with comprehensive authentication, JWT tokens, and real-time subscription support.
- GraphQL API with WebSocket subscriptions for real-time updates
- REST API with comprehensive HTTP endpoints for all operations
- OpenAPI 3.0 Documentation with interactive Swagger UI
- Both APIs share the same authentication system
- Environment-Based Authentication with configurable credentials
- Multiple Authentication Methods:
- Username/Password with JWT tokens
- Direct access tokens (non-JWT)
- Read-only user support
- Role-Based Access Control (admin vs read-only)
- Token Management with automatic expiration and renewal
- Data point CRUD operations and value management
- Data point type structure management
- Tag queries with metadata (value, timestamp, status)
- Alert handling and historical queries
- CNS (Central Navigation Service) operations
- System information and redundancy status
- OPC UA address configuration
- Interactive API Documentation at
/api-docs - Health Check Endpoint for monitoring
- Comprehensive Error Handling and logging
- CORS Support with configurable origins
-
Install Node.js dependencies:
npm install
-
Install the dotenv package if not already installed:
npm install dotenv
The server uses a .env file for configuration, which must be placed in the same directory as index.js. The server will automatically look for the .env file in its own directory, regardless of where it's started from.
Step 1: Copy the example environment file:
cp .env.example .envStep 2: Edit the .env file with your configuration:
# GraphQL Server Configuration
# Copy this file to .env and adjust the values
# Server Port
GRAPHQL_PORT=4000
# Authentication Settings
# Set to true to disable all authentication (not recommended for production)
DISABLE_AUTH=false
# JWT Secret Key (change this in production!)
JWT_SECRET=your-secret-key-change-in-production
# Admin User Credentials
# If set, these credentials can be used to login with full access
ADMIN_USERNAME=admin
ADMIN_PASSWORD=changeme
# Direct Access Token (non-JWT)
# If set, this token can be used directly in the Authorization header for full access
# Example: Authorization: Bearer your-direct-access-token
DIRECT_ACCESS_TOKEN=
# Read-Only User Credentials
# If set, these credentials provide read-only access (queries only, no mutations)
READONLY_USERNAME=readonly
READONLY_PASSWORD=readonly123
# Read-Only Direct Access Token (non-JWT)
# If set, this token provides direct read-only access
# Example: Authorization: Bearer your-readonly-token
READONLY_TOKEN=
# Token Expiry (in milliseconds)
# Default: 3600000 (1 hour)
TOKEN_EXPIRY_MS=3600000
# Logging Level
# Options: debug, info, warn, error
LOG_LEVEL=infowinccoa-graphql-server/
├── index.js # Main server file
├── .env # Configuration file (create this)
├── .env.example # Example configuration
├── package.json
├── public/ # Static web assets
│ └── index.html # Landing page
├── graphql/ # GraphQL-related files
│ ├── common.gql # Common schema
│ ├── common.js # Common resolvers
│ ├── alerting.gql # Alert schema
│ ├── alerting.js # Alert resolvers
│ ├── cns.gql # CNS schema
│ ├── cns.js # CNS resolvers
│ ├── extras.gql # Extras schema
│ ├── extras.js # Extras resolvers
│ └── subscriptions.js # Subscription resolvers
└── restapi/ # REST API files
├── rest-api.js # REST API router
├── openapi.js # OpenAPI loader
├── openapi-full.yaml # Complete OpenAPI spec
├── REST-API.md # REST API documentation
└── routes/ # REST endpoint routes
├── auth-routes.js
├── datapoint-routes.js
├── datapoint-type-routes.js
├── tag-routes.js
├── alert-routes.js
├── cns-routes.js
├── system-routes.js
└── extras-routes.js
Important: The .env file must be placed in the root directory (same directory as index.js).
The server automatically detects its own directory and looks for the .env file there, so it will work regardless of where you start the server from.
When the server starts, it will display detailed information about:
- Environment file loading status
- Which credentials are configured
- Authentication warnings and recommendations
Example startup output:
Looking for .env file at: /path/to/winccoa-graphql-server/.env
✅ .env file loaded successfully
Loaded variables: GRAPHQL_PORT, ADMIN_USERNAME, ADMIN_PASSWORD, JWT_SECRET, ...
Starting GraphQL server on port 4000 with DISABLE_AUTH=false
🔐 Authentication Configuration:
Admin Username: ✅ Set
Admin Password: ✅ Set
Direct Access Token: ❌ Not set
Readonly Username: ✅ Set
Readonly Password: ✅ Set
Readonly Token: ❌ Not set
JWT Secret: ✅ Custom
Token Expiry: 3600000ms (60 minutes)
✅ Authentication is properly configured.
You can configure multiple authentication methods:
-
Username/Password Authentication: Set
ADMIN_USERNAMEandADMIN_PASSWORDfor full access, orREADONLY_USERNAMEandREADONLY_PASSWORDfor read-only access. -
Direct Token Authentication: Set
DIRECT_ACCESS_TOKENfor full access orREADONLY_TOKENfor read-only access without requiring login. -
Development Mode: If no credentials are configured, you can use
dev/devfor development.
# Using WinCC OA bootstrap
node "/opt/WinCC_OA/3.20/javascript/winccoa-manager/lib/bootstrap.js" -PROJ <project-name> -pmonIndex <nr> winccoa-graphql-server/index.js
# Or for development
npm run dev- Home:
http://localhost:4000/- Interactive landing page with quick access to all endpoints
- GraphQL Endpoint:
http://localhost:4000/graphql - WebSocket Endpoint:
ws://localhost:4000/graphql
- Base URL:
http://localhost:4000/restapi - Interactive Documentation:
http://localhost:4000/api-docs - OpenAPI Specification:
http://localhost:4000/openapi.json
- Health Check:
http://localhost:4000/restapi/health
- You need real-time updates via WebSocket subscriptions
- You want to fetch multiple related resources in a single request
- You need flexible queries with only the fields you want
- You're building a modern web/mobile app with complex data requirements
- You prefer traditional HTTP methods (GET, POST, PUT, DELETE)
- You want simple, predictable URL structures
- You're integrating with systems that work better with REST
- You need to cache responses at the HTTP level
- You want to explore the API interactively via Swagger UI
Both APIs provide the same functionality and share authentication!
Both GraphQL and REST APIs use the same authentication system with JWT tokens.
GraphQL:
mutation {
login(username: "admin", password: "your-password") {
token
expiresAt
}
}REST API:
curl -X POST http://localhost:4000/restapi/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"your-password"}'Response:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiresAt": "2024-01-01T13:00:00.000Z"
}Then use the returned JWT token in subsequent requests:
Authorization: Bearer <jwt-token>
Use the configured direct access token directly (works for both GraphQL and REST):
Authorization: Bearer your-direct-access-token
- Can perform all queries and mutations
- Full access to all GraphQL operations
- Configured via
ADMIN_USERNAME/ADMIN_PASSWORDorDIRECT_ACCESS_TOKEN
- Can only perform queries
- Mutations are blocked with "Forbidden" error
- Configured via
READONLY_USERNAME/READONLY_PASSWORDorREADONLY_TOKEN
- Start the server:
npm run dev - Open your browser to
http://localhost:4000/ - Click on any of the API buttons:
- GraphQL API - For flexible queries and real-time subscriptions
- REST API Docs - Interactive Swagger UI with try-it-out
- Health Check - Server status monitoring
- Go to
http://localhost:4000/api-docs - Click the "Authorize" button and enter your token
- Try out any endpoint directly from the browser!
# 1. Login
mutation {
login(username: "admin", password: "your-password") {
token
expiresAt
}
}
# 2. Query with token
query {
dpGet(dpeNames: ["System1:ExampleDp.value"])
}
# 3. Mutation (admin only)
mutation {
dpSet(dpeNames: ["System1:ExampleDp.value"], values: [42])
}# Login as read-only user
mutation {
login(username: "readonly", password: "readonly-password") {
token
}
}
# Queries work
query {
dpTypes
}
# Mutations are blocked
mutation {
dpSet(dpeNames: ["System1:ExampleDp.value"], values: [42])
# Returns: "Forbidden: Read-only users cannot perform mutations"
}For WebSocket connections, pass the token in connection parameters:
const client = createClient({
url: 'ws://localhost:4000/graphql',
connectionParams: {
"Authorization": "Bearer <your-token>"
}
});# 1. Login
curl -X POST http://localhost:4000/restapi/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"your-password"}'
# Response: {"token":"eyJhbG...", "expiresAt":"2024-01-01T13:00:00.000Z"}
# 2. Get data point value
curl http://localhost:4000/restapi/datapoints/ExampleDP_Arg1.value/value \
-H "Authorization: Bearer eyJhbG..."
# Response: {"value": 42.5}curl -X PUT http://localhost:4000/restapi/datapoints/ExampleDP_Arg1.value/value \
-H "Authorization: Bearer eyJhbG..." \
-H "Content-Type: application/json" \
-d '{"value": 50.0}'
# Response: {"success": true}curl "http://localhost:4000/restapi/datapoints?pattern=ExampleDP_*" \
-H "Authorization: Bearer eyJhbG..."
# Response: {"datapoints": ["ExampleDP_Arg1.", "ExampleDP_Arg2."]}curl "http://localhost:4000/restapi/tags?dpeNames=ExampleDP_1.value,ExampleDP_2.value" \
-H "Authorization: Bearer eyJhbG..."
# Response: {
# "tags": [
# {
# "name": "ExampleDP_1.value",
# "value": 42.5,
# "timestamp": "2024-01-01T12:00:00Z",
# "status": {"_online": {"_value": true}}
# }
# ]
# }curl -X POST http://localhost:4000/restapi/extras/opcua/address \
-H "Authorization: Bearer eyJhbG..." \
-H "Content-Type: application/json" \
-d '{
"datapointName": "TestMe.",
"driverNumber": 2,
"addressDirection": 2,
"addressDataType": 750,
"serverName": "OpcUaServer",
"subscriptionName": "Sub1",
"nodeId": "ns=2;s=MyNode"
}'
# Response: {"success": true}For complete REST API documentation, see REST-API.md or visit the interactive documentation at http://localhost:4000/api-docs.
- Environment-based credential configuration
- JWT tokens with configurable expiration
- Direct token support for API integrations
- Proper GraphQL operation parsing (prevents comment bypass attacks)
- Admin users: Full access to queries and mutations
- Read-only users: Query access only, mutations blocked
- Proper permission checking after GraphQL parsing
- Automatic token expiration and cleanup
- Token validity extension on each request
- Secure token validation
Visit http://localhost:4000/api-docs for interactive REST API documentation where you can:
- Browse all available endpoints
- See request/response schemas
- Try out API calls directly from your browser
- Authenticate and test with your credentials
- OpenAPI 3.0 JSON:
http://localhost:4000/openapi.json - YAML File:
openapi-full.yamlin the project root - Import into tools like Postman, Insomnia, or generate client SDKs
- REST API Reference: REST-API.md - Complete REST endpoint documentation with examples
- GraphQL Schema: Available via introspection at the GraphQL endpoint
- OpenAPI YAML: openapi-full.yaml - Complete OpenAPI 3.0 specification
| Variable | Required | Default | Description |
|---|---|---|---|
GRAPHQL_PORT |
No | 4000 | Server port (used by both GraphQL and REST) |
DISABLE_AUTH |
No | false | Disable all authentication ( |
JWT_SECRET |
Yes | - | JWT signing secret (change in production!) |
TOKEN_EXPIRY_MS |
No | 3600000 | Token expiry time in milliseconds |
ADMIN_USERNAME |
No | - | Admin username |
ADMIN_PASSWORD |
No | - | Admin password |
DIRECT_ACCESS_TOKEN |
No | - | Direct access token for full access |
READONLY_USERNAME |
No | - | Read-only username |
READONLY_PASSWORD |
No | - | Read-only password |
READONLY_TOKEN |
No | - | Direct read-only token |
CORS_ORIGIN |
No | * | CORS allowed origins (comma-separated) |
LOG_LEVEL |
No | info | Logging level (debug, info, warn, error) |
Run the authentication tests:
# Test environment-based authentication
node test-env-auth.js
# Test basic functionality
node test-graphql.js- ✅ Configure Strong Credentials: Set secure usernames and passwords in
.env - ✅ Change JWT Secret: Use a strong, unique
JWT_SECRET - ✅ Use HTTPS: Deploy behind HTTPS proxy
- ✅ Secure Environment: Keep
.envfile secure and not in version control - ✅ Token Security: Use strong direct access tokens if needed
- ✅ Monitor Access: Log and monitor authentication attempts
- Use strong, unique passwords for admin and readonly users
- Set a cryptographically secure JWT_SECRET (32+ characters)
- Configure appropriate token expiry times
- Use direct tokens only for trusted API integrations
- Deploy behind a reverse proxy with HTTPS
- Implement rate limiting and monitoring
- "Cannot find module 'dotenv'": Run
npm install dotenvto install the dotenv package - ".env file not found or could not be loaded":
- Ensure the
.envfile exists in the same directory asindex.js - Check file permissions (must be readable)
- Verify the file is named exactly
.env(not.env.txtor similar)
- Ensure the
- Environment variables not loaded:
- Check the startup log for "Looking for .env file at: ..." to see the expected path
- Ensure
.envfile format is correct (KEY=value, no spaces around =) - Check for syntax errors in the .env file
- Server shows "No authentication credentials configured":
- Check that your
.envfile containsADMIN_USERNAMEandADMIN_PASSWORD - Verify the server was restarted after creating/modifying the
.envfile - Look at the startup configuration display to see which values are loaded
- Check that your
- "Unauthorized" errors:
- Check that credentials in
.envmatch what you're using for login - Ensure the server was restarted after changing
.env - Check the server logs for authentication debugging information
- Check that credentials in
- "Invalid username or password":
- Verify the exact username/password in your
.envfile - Check for extra spaces or special characters
- Try the development credentials
dev/devif no .env is configured
- Verify the exact username/password in your
- "Forbidden" for read-only users: This is expected behavior for mutations
- Direct tokens not working:
- Ensure server was restarted after adding tokens to
.env - Check that
DIRECT_ACCESS_TOKENorREADONLY_TOKENare set correctly - Verify you're using the exact token string in the Authorization header
- Ensure server was restarted after adding tokens to
- Development Mode: If no credentials are configured in
.env, usedev/devfor testing - Production Mode: Always configure proper credentials in
.envand restart the server
Check the server startup output for diagnostic information:
# Good startup (with .env file):
Looking for .env file at: /path/to/.env
✅ .env file loaded successfully
🔐 Authentication Configuration: [shows configured options]
✅ Authentication is properly configured.
🚀 GraphQL server ready at http://localhost:4000/graphql
🔌 WebSocket subscriptions ready at ws://localhost:4000/graphql
🌐 REST API ready at http://localhost:4000/restapi
📚 API documentation at http://localhost:4000/api-docs
📄 OpenAPI spec at http://localhost:4000/openapi.json
# Missing .env file:
Looking for .env file at: /path/to/.env
⚠️ .env file not found or could not be loaded: ENOENT: no such file or directory
⚠️ WARNING: No authentication credentials configured!
# Dotenv module missing:
Error: Cannot find module 'dotenv'
# Solution: Run "npm install dotenv"┌─────────────────────────────────────────────────────────┐
│ HTTP Server (Port 4000) │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────┐ ┌──────────────┐ ┌─────────────┐ │
│ │ GraphQL │ │ REST API │ │ Swagger UI │ │
│ │ /graphql │ │ /restapi/* │ │ /api-docs │ │
│ │ │ │ │ │ │ │
│ │ - Queries │ │ - GET │ │ - Browse │ │
│ │ - Mutations │ │ - POST │ │ - Try out │ │
│ │ - WebSocket │ │ - PUT │ │ - Auth │ │
│ │ (real-time)│ │ - DELETE │ │ │ │
│ └───────┬───────┘ └──────┬───────┘ └─────────────┘ │
│ │ │ │
│ └─────────┬───────┘ │
│ │ │
│ ┌──────────▼──────────┐ │
│ │ Shared Auth Layer │ │
│ │ - JWT Tokens │ │
│ │ - Role-Based Auth │ │
│ └──────────┬──────────┘ │
│ │ │
│ ┌──────────▼──────────┐ │
│ │ WinCC OA Manager │ │
│ │ - Data Points │ │
│ │ - Alerts │ │
│ │ - CNS │ │
│ └─────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
Key Benefits of Dual Interface:
- Use GraphQL for modern apps with real-time needs
- Use REST for traditional integrations and easy testing
- Share authentication tokens between both APIs
- Explore and test via Swagger UI before writing code
- One server, two complete APIs, same powerful functionality