API Key vs OAuth2 vs JWT Bearer
The SGT Systems platform supports three authentication mechanisms. They overlap but are optimised for different scenarios. Choosing the right one up-front saves significant pain later — particularly around credential rotation, revocation, and audit.
| Mechanism | Best for | Identity | Lifetime | Revocable |
|---|---|---|---|---|
| API Key | Server-to-server integrations, scripts, ETL jobs. | A service account | Long-lived (until rotated) | Yes, instantly |
| OAuth2 (auth code + PKCE) | End-user interactive apps acting on a human's behalf. | A specific user | Short access token (1h) + refresh token | Yes |
| JWT Bearer | Federated SSO, partner systems with their own IdP. | Asserted by partner IdP | Short-lived (≤ 15 min recommended) | Indirectly (rotate signing key) |
Rule of thumb: if a human is in the loop, use OAuth2. If the integration is a backend service acting as itself, use an API key. JWT Bearer is for the case where you already have an enterprise identity provider and want to mint short-lived tokens for your own services to call SGT.
Concrete examples from the SGT customer base:
| Scenario | Right choice |
|---|---|
| Nightly ERP sync pulling device list and asset metadata | API key with devices:read |
| Customer-facing mobile app letting end users acknowledge alarms | OAuth2 auth code + PKCE |
| On-prem analytics service inside a customer's AD environment | JWT Bearer using their IdP |
| CI pipeline running integration tests against the sandbox | Short-lived API key, expiry 7 days |
| BI dashboard fetching aggregates every 5 minutes | API key with telemetry:read only |
Generating an API Key from the SGT Admin
- Sign in to the SGT admin at
https://admin.sgtsystems.comwith an account that has the Integrations → Manage keys permission. - Navigate to Settings → API Keys.
- Click New key. You will be asked for:
- A human-readable label (e.g.
acme-erp-sync). - The scopes to grant (see below — grant the minimum).
- Optional IP allowlist in CIDR notation.
- Optional expiry date (recommended: 90 days for production, 30 for dev).
- A human-readable label (e.g.
- The key is displayed once. Copy it immediately into your secret manager — the platform never shows it again. You can however generate a replacement at any time.
Issued keys are prefixed so they're easy to spot in logs and easy to scope-scan in source control:
sgt_live_…production trafficsgt_test_…sandbox / staging environmentsgt_dev_…personal developer keys (heavier rate-limit, telemetry-quarantined tenant)
The prefix is followed by a high-entropy random suffix (256 bits, base62 encoded). Keys are stored in our database as a salted SHA-256 hash; even SGT staff cannot retrieve a key value once it has been generated. If you lose it, generate a new one.
Key Rotation & Revocation
SGT recommends rotating every long-lived API key on a fixed schedule (90 days for production, 30 for non-production). The admin supports a dual-key rotation window so you never have downtime:
- From the key's detail page, click Rotate. A new key value is generated; the old value remains valid.
- Deploy the new value to your secret manager and roll out your services.
- When you're confident no caller is still using the old value (check the Last used column or the audit log), click Revoke previous.
For incident response, the Revoke now button immediately invalidates a key — any in-flight requests will fail with 401 unauthenticated within seconds. Revocation propagates to all edge nodes via a pub/sub channel; in practice it is effective within 2 seconds globally.
Scopes & Least-Privilege Access
API keys are scoped. A scope is a coarse capability over a resource family. Grant only the scopes a key actually needs. Scopes follow a <resource>:<action> shape, with :write implicitly granting :read for the same resource.
| Scope | Grants |
|---|---|
devices:read | List and read devices. |
devices:write | Create, update, and decommission devices. |
telemetry:read | Query telemetry and aggregates. |
telemetry:ingest | Push telemetry readings. |
events:read | List and view events / alarms. |
events:write | Acknowledge and clear alarms. |
reports:run | Trigger reports and read results. |
webhooks:manage | Register and update webhook endpoints. |
admin:audit:read | Read the tenant audit log. |
admin:* | Full administrative access. Reserve for break-glass keys; never for everyday integrations. |
Most integrations need only two or three scopes. A read-only dashboard might use devices:read + telemetry:read + events:read. An ingest worker might use telemetry:ingest alone. Resist the temptation to grant admin:* "to make the prototype work" — it almost always survives into production, and the blast radius of a leak then includes destructive operations.
Beyond scopes, keys also carry an optional resource filter — e.g. limit this key to sites dhaka-warehouse-1 and dhaka-warehouse-2 only. This is the most powerful tool for limiting blast radius across a multi-site tenant: an integration for one site cannot accidentally affect another.
Sample Authentication Headers
API key (curl)
curl -sS https://api.sgtsystems.com/v1/devices \
-H "Authorization: Bearer sgt_live_5h7k9w...truncated" \
-H "X-SGT-Tenant: acme-industries"
OAuth2 access token (curl)
curl -sS https://api.sgtsystems.com/v1/devices \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
-H "X-SGT-Tenant: acme-industries"
JWT Bearer with assertion (curl)
# Exchange a signed JWT issued by your IdP for an SGT access token
curl -sS -X POST https://auth.sgtsystems.com/oauth2/token \
-d grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer \
-d assertion="$YOUR_SIGNED_JWT" \
-d scope="devices:read telemetry:read"
# Use the returned access_token on subsequent calls
curl -sS https://api.sgtsystems.com/v1/devices \
-H "Authorization: Bearer $ACCESS_TOKEN"
OAuth2 authorization-code URL
https://auth.sgtsystems.com/oauth2/authorize
?response_type=code
&client_id=your_client_id
&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback
&scope=devices:read+events:read
&state=opaque_csrf_value
&code_challenge=BASE64URL(SHA256(verifier))
&code_challenge_method=S256
Secret Storage Best Practices
- Never commit keys to source control. Use a
.envfile gitignored at the repo root, and a real secret manager in production (AWS Secrets Manager, HashiCorp Vault, GCP Secret Manager, 1Password Connect). - Inject secrets at runtime, not build time. Building a key into a container image effectively makes it a long-lived secret bound to every layer that ever runs that image.
- Scope by environment. Production keys never leave production. Developers use
sgt_dev_*keys with restricted scope and rate limits. - Use a secret-scanning tool (GitGuardian, gitleaks, GitHub's built-in scanner). SGT keys carry consistent prefixes precisely so these tools can detect them.
- Encrypt at rest, decrypt in memory. Avoid logging secrets, including in stack traces.
- Don't share keys between services. Each microservice should hold its own key with its own scope. Blast radius matters.
- Use short-lived credentials wherever possible. If you can use OAuth2 or JWT Bearer instead of a long-lived API key, do.
- Have a runbook for compromise. Document, in advance, exactly which key powers which integration and who has authority to revoke and re-issue at 3 a.m.
IP Allowlisting
An optional per-key IP allowlist provides defence in depth. Requests presenting a valid key but originating outside the allowlist receive 403 forbidden.
Allowlist examples:
203.0.113.42/32 # single static NAT egress
198.51.100.0/24 # an entire office subnet
2001:db8:acme::/48 # IPv6 prefix
Caveats:
- Cloud workloads usually rotate egress IPs unless you front them with a NAT gateway with reserved IPs. Plan accordingly.
- Mobile clients should not use IP allowlisting — use OAuth2 instead.
- Combine with signature checks on webhooks; allowlisting alone is not authentication.
- Beware of NAT-shared egress at large offices — an allowlist that admits one team's traffic admits the whole building's.
Audit Logging
Every authenticated request is recorded in the tenant audit log with the following fields:
| Field | Description |
|---|---|
ts | ISO-8601 timestamp. |
actor.type | api_key, user, or service. |
actor.id | Stable identifier of the key or user. |
actor.label | Human label of the key (e.g. acme-erp-sync). |
method / path | HTTP request line. |
status | HTTP status returned. |
ip | Source IP at the edge. |
user_agent | Client User-Agent string. |
request_id | Correlate with the platform's internal traces. |
The audit log is queryable via GET /v1/audit-log (requires admin:audit:read) and is retained for 13 months. It is the first place to look after rotating a key or investigating any suspicious activity. The log is append-only and tamper-evident: each entry includes a hash chained to the previous entry's hash, so any retroactive modification is detectable.
Useful audit queries to run on a schedule:
- Keys with zero usage in the past 30 days — candidates for revocation.
- Requests from previously-unseen IP prefixes — possible compromise indicator.
- Spikes in
4xxrates per key — misbehaving integration or attempted abuse. - Any
admin:*activity outside business hours.
Compliance Notes
For customers operating under specific compliance regimes:
- ISO 27001 — the rotation, audit, and least-privilege practices above satisfy the credential-management control set.
- SOC 2 — quarterly evidence reports can be generated directly from the audit log; export to PDF/CSV via the admin UI.
- PCI DSS (where SGT data touches cardholder environments) — use IP allowlisting and 90-day rotation, both supported above.
Need help?
For help designing a least-privilege scope set, setting up OAuth2 for a partner app, or investigating a suspected key compromise, contact the SGT Systems security team at support@sgtsystems.com or via our contact page.