Authentication & Authorization
How to authenticate and manage access to Konnektr Graph
Konnektr Graph uses OAuth 2.1 with JWT Bearer tokens for authentication and fine-grained, permission-based authorization for access control.
Client Credentials
Konnektr Graph validates OAuth 2.1 JWT bearer tokens issued by the identity provider you configure for your deployment. Every caller needs a client registration in that provider.
Getting Your Credentials
Register a client application in your identity provider. Your credentials include:
- Client ID - Identifies your application
- Client Secret - Secret key for authentication
- Audience - The API identifier (e.g.,
api://konnektr-graph) - Authority - The OAuth server URL (e.g.,
https://auth.example.com)
Authenticating Requests
Use OAuth 2.1 Client Credentials flow to get an access token:
curl -X POST https://auth.example.com/oauth/token \
-H "Content-Type: application/json" \
-d '{
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"audience": "api://konnektr-graph",
"grant_type": "client_credentials"
}'Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 86400
}Use the access token in all API requests:
curl https://graph.example.com/digitaltwins/twin-1 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"Using Azure Digital Twins SDKs
You can use the standard Azure Digital Twins SDKs with Konnektr Graph:
# Python
from azure.digitaltwins.core import DigitalTwinsClient
from azure.core.credentials import AccessToken
from datetime import datetime
import requests
# Get token from your OAuth provider
token_response = requests.post(
"https://auth.example.com/oauth/token",
json={
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"audience": "api://konnektr-graph",
"grant_type": "client_credentials"
}
)
access_token = token_response.json()["access_token"]
# Create custom credential
class StaticTokenCredential:
def __init__(self, token):
self.token = token
def get_token(self, *scopes, **kwargs):
expires_on = int(datetime.now().timestamp()) + 3600
return AccessToken(self.token, expires_on)
# Connect to Konnektr Graph
client = DigitalTwinsClient(
"https://graph.example.com",
StaticTokenCredential(access_token)
)
# Use the client
twin = client.get_digital_twin("my-twin-id")Managing Permissions
Permissions are either embedded in the token your identity provider issues, or fetched per request from a permission API you run. See Permission Providers for both options.
Common Permissions:
| Permission | Description |
|---|---|
digitaltwins/read | Read digital twins and components |
digitaltwins/write | Create and update digital twins |
digitaltwins/delete | Delete digital twins |
digitaltwins/relationships/read | Read relationships |
digitaltwins/relationships/write | Create and update relationships |
digitaltwins/relationships/delete | Delete relationships |
models/read | Read DTDL models |
models/write | Create and update models |
query/read | Execute Cypher queries |
Identity Provider Configuration
Konnektr Graph does not ship an identity provider. Point it at your own OAuth 2.1 / OpenID Connect provider.
Supported Providers
Konnektr Graph works with any OAuth 2.1 / OpenID Connect provider:
- Auth0
- Azure AD / Microsoft Entra ID
- Keycloak
- Okta
- Custom OAuth 2.1 server
Configuration
Set these environment variables or update appsettings.json:
API Service:
# Enable authentication
Authentication__Enabled=true
Authentication__Authority=https://auth.example.com
Authentication__Audience=api://konnektr-graph
Authentication__Issuer=https://auth.example.com/
# Enable authorization
Authorization__Enabled=true
Authorization__Provider=Claims
Authorization__PermissionsClaimName=permissionsJWT Token Requirements
Your OAuth provider must issue JWT tokens with these claims:
{
"sub": "user-or-service-account-id",
"aud": "api://konnektr-graph",
"iss": "https://auth.example.com/",
"scope": "digitaltwins:read",
"permissions": [
"digitaltwins/read",
"digitaltwins/write",
"digitaltwins/read"
]
}Required Claims:
sub- Subject (user/service account identifier)aud- Audience (must match your configured audience)iss- Issuer (must match your configured issuer)
Optional but Recommended:
scope- OAuth scopes required by your API (e.g.digitaltwins:read)permissions- Array of permission strings
Permission Providers
You can choose how permissions are provided:
1. JWT Claims (Default)
Permissions are embedded in the JWT token's permissions claim:
{
"Authorization": {
"Provider": "Claims",
"PermissionsClaimName": "permissions"
}
}Pros:
- Fast (no external calls)
- Works offline
- Simple setup
Cons:
- Permissions baked into token (can't revoke until expiry)
- Larger token size
2. API Provider
Fetch permissions from an external API on each request:
{
"Authorization": {
"Provider": "Api",
"ApiProvider": {
"BaseUrl": "https://your-permission-api.com",
"CheckEndpoint": "/api/v1/permissions/check",
"ResourceName": "graph-instance-123",
"CacheExpirationMinutes": 5,
"TimeoutSeconds": 10
}
}
}Pros:
- Dynamic permissions (can revoke immediately)
- Centralized permission management
- Smaller token size
Cons:
- Requires external API call
- Potential latency
- Dependency on external service
Proxy Delegation
When the API service runs behind an authenticating proxy that uses a managed identity to forward requests, the delegated user ID from an HTTP header can be used instead of the managed identity's sub claim. This is configured via:
{
"Parameters": {
"UserIdHeaderName": "X-User-Id",
"UserIdHeaderRequired": false
}
}When enabled, the UserIdHeaderMiddleware reads the header value (e.g., a B2C user ID injected by the proxy) and creates a synthetic ClaimsIdentity prepended to the ClaimsPrincipal. All downstream authorization checks and TrackLastUpdatedBy use this synthetic identity.
For the Api permission provider, the synthetic identity's sub claim (from the header) is used for permission lookups. For the Claims provider, original managed identity permission claims are preserved and accessible.
See Security for details.
Configuring Your Auth Provider
Auth0 Example
-
Create an API in Auth0 Dashboard
- Name:
Konnektr Graph - Identifier:
api://konnektr-graph
- Name:
-
Create a Machine-to-Machine Application
- Authorize it for your API
- Note the Client ID and Secret
-
Add Permissions (in API settings)
- Add each permission as a scope (e.g.,
digitaltwins:read,models/read)
- Add each permission as a scope (e.g.,
-
Configure Rules (optional)
- Add custom claims to include permissions array
Azure AD Example
-
Register an Application
- Note the Application (client) ID
- Create a client secret
-
Expose an API
- Set Application ID URI:
api://konnektr-graph - Add scopes for each permission
- Set Application ID URI:
-
App Roles (optional)
- Define app roles for permission groups
- Assign users/service principals to roles
-
Token Configuration
- Add optional claims to include permissions
OAuth Discovery
The API service exposes an OAuth discovery endpoint at:
/.well-known/oauth-protected-resourceThis endpoint returns RFC 9728 Protected Resource Metadata:
{
"resource": "https://your-server.com",
"authorization_servers": ["https://auth.example.com"],
"scopes_supported": ["digitaltwins:read", "digitaltwins:write"],
"bearer_methods_supported": ["header"]
}Error Responses
Understanding error responses helps troubleshoot authentication and authorization issues.
401 Unauthorized
Returned when no valid JWT token is provided:
{
"error": "unauthorized",
"error_description": "Authentication required. Please provide a valid Bearer token.",
"resource_metadata": "https://your-server/.well-known/oauth-protected-resource"
}HTTP Headers:
WWW-Authenticate: Bearer realm="graph", resource_metadata="https://..."Common Causes:
- Missing
Authorizationheader - Expired token
- Invalid token signature
- Token from wrong issuer
Solution: Get a fresh access token from your OAuth provider.
403 Forbidden - Missing Scope
Returned when OAuth scope is missing:
{
"error": "insufficient_scope",
"error_description": "Required scopes: digitaltwins:read",
"scope": "digitaltwins:read"
}Common Causes:
- Token doesn't include required OAuth scope
- Scope not configured in OAuth provider
Solution:
- Configure your OAuth provider to include the scope in issued tokens
403 Forbidden - Missing Permission
Returned when permission is missing:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.3",
"title": "Forbidden",
"status": 403,
"detail": "User lacks required permission: digitaltwins/read"
}Common Causes:
- User/service account doesn't have required permission
- Permission not included in JWT token
- Permission API call failed (if using API provider)
Solution:
- Add the permission to the JWT token, or configure your permission API to return it
Troubleshooting
Debugging Authentication
Check your token:
# Decode JWT (using jwt.io or jwt-cli)
echo "YOUR_TOKEN" | jwt decode -
# Verify claims:
# - aud: Must match your configured audience
# - iss: Must match your configured issuer
# - exp: Must be in the future
# - permissions: Must include required permissionsTest authentication:
# Try accessing a public endpoint
curl https://your-server/.well-known/oauth-protected-resource
# Try with your token
curl -H "Authorization: Bearer YOUR_TOKEN" \
https://your-server/digitaltwins/twin-1Common Issues
Issue: "401 Unauthorized" with valid token
Possible causes:
- Token audience doesn't match configured audience
- Token issuer doesn't match configured issuer
- Clock skew (token not yet valid or expired)
Solution: Check your Authentication:Audience and Authentication:Issuer configuration.
Issue: "403 Forbidden" for all requests
Possible causes:
- Permissions not configured
- Wrong permission claim name
- Permission provider not working
Solution:
- Check
Authorization:PermissionsClaimNamematches your token - Verify permissions are in the token
- Check logs for permission provider errors
Enabling Debug Logging
Enable verbose authentication logging:
Add to appsettings.Development.json:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore.Authentication": "Debug",
"Microsoft.AspNetCore.Authorization": "Debug",
"AgeDigitalTwins.ServiceDefaults.Authorization": "Debug"
}
}
}This logs:
- JWT validation steps
- Permission checks
- Authorization decisions
Security Best Practices
Token Management
- Rotate secrets regularly - Change client secrets every 90 days
- Use short token expiry - Keep tokens valid for 1 hour or less
- Secure token storage - Never commit tokens to source control
- Use HTTPS only - Always use encrypted connections
Permission Design
- Principle of least privilege - Grant minimum permissions needed
- Separate read/write - Don't use wildcards unless necessary
- Audit permissions - Review access regularly
- Use service accounts - Dedicated credentials for applications
Operational Considerations
- Secure OAuth provider - Use established providers (Auth0, Azure AD)
- Monitor auth logs - Watch for suspicious patterns
- Implement rate limiting - Prevent brute force attacks
- Keep software updated - Apply security patches promptly
Further Reading
- RFC 9728: Protected Resource Metadata - OAuth discovery standard
- OAuth 2.1 Specification - Latest OAuth standard