Install
$ agentstack add skill-impertio-studio-speckle-claude-skill-package-speckle-errors-auth ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
speckle-errors-auth
Quick Reference
Authentication Error Decision Tree
Error encountered
├── HTTP 401 / GraphQL "UNAUTHENTICATED"
│ ├── No token provided → Add Authorization: Bearer header
│ ├── Token malformed → Check for whitespace, newlines, or truncation in token string
│ ├── Token expired → Refresh token (OAuth) or generate new PAT
│ ├── Token revoked → Generate new token, check if admin revoked it
│ └── Wrong server URL → Verify token matches the target server instance
│
├── HTTP 403 / GraphQL "FORBIDDEN"
│ ├── Missing scope → Check required scope for the operation (see Scope Table)
│ ├── Wrong project role → User needs contributor/owner, has only viewer
│ ├── Resource-scoped token → Token is limited to specific projects/workspaces
│ └── Server role insufficient → User needs SERVER_USER or SERVER_ADMIN role
│
├── GraphQL "NOT_FOUND" (may mask auth error)
│ └── User lacks access to resource → Speckle returns NOT_FOUND instead of FORBIDDEN
│ for resources the user cannot see at all
│
├── OAuth flow failure
│ ├── Challenge mismatch → ALWAYS store and reuse the SAME challenge string
│ ├── Invalid redirect URI → URI MUST exactly match the registered redirect URI
│ ├── Expired access code → Exchange the code immediately after callback
│ └── Wrong appId/appSecret → Verify credentials match the registered application
│
└── Refresh token failure
├── Refresh token expired → User MUST re-authenticate from scratch
├── Refresh token already used → Tokens are single-use; store the NEW refresh token
└── Wrong appId/appSecret → MUST use the same app credentials as original auth
Error Code Quick Map
| Error | Protocol | Meaning | First Check | |-------|----------|---------|-------------| | 401 Unauthorized | REST | No valid token present | Is Authorization: Bearer header set? | | 403 Forbidden | REST | Valid token, insufficient permissions | Does token have required scopes? | | UNAUTHENTICATED | GraphQL | Token missing, invalid, or expired | Is token valid? Test with activeUser query | | FORBIDDEN | GraphQL | Token valid but lacks required scope or role | Check scope table below | | NOT_FOUND | GraphQL | Resource missing OR user lacks all access | Verify resource exists with an admin account |
Scope Requirements Table
| Operation | Required Scope | Required Role | |-----------|---------------|---------------| | Read projects/models/versions | streams:read | Any project role | | Create/modify/delete projects | streams:write | Contributor or Owner | | Delete a project | streams:write | Owner only | | Read user profile | profile:read | — | | Read user email | profile:email | — | | Update user profile | profile:write | — | | List API tokens | tokens:read | — | | Create/revoke API tokens | tokens:write | — | | Search/list users | users:read | — | | Server admin operations | server:setup | SERVER_ADMIN |
401 vs 403: The Critical Distinction
401 Unauthorized (UNAUTHENTICATED)
The server does NOT recognize the caller as a valid user.
Causes (in order of likelihood):
- Missing Authorization header — The request has no
Authorizationheader at all - Malformed token — Extra whitespace, newline characters, or truncated string
- Expired token — OAuth access tokens have a limited lifetime
- Revoked token — Admin or user revoked the token
- Wrong server — Token was generated for
app.speckle.systemsbut sent to a self-hosted instance (or vice versa)
Diagnostic steps:
# Step 1: Test token validity: this query requires only profile:read
query { activeUser { id name } }
- If result is
null→ token is invalid or missingprofile:readscope - If result contains user data → token is valid; problem is elsewhere
# SpecklePy: Test authentication
from specklepy.api.client import SpeckleClient
client = SpeckleClient(host="https://app.speckle.systems")
client.authenticate_with_token("YOUR_TOKEN")
# If this raises, the token is invalid for this server
user = client.active_user.get()
print(f"Authenticated as: {user.name}")
403 Forbidden (FORBIDDEN)
The server recognizes the caller but DENIES the requested operation.
Causes (in order of likelihood):
- Missing scope on token — Token was created without the required scope
- Insufficient project role — User is viewer but operation requires contributor/owner
- Resource-scoped token — Token is limited to specific projects; current project is not in the allowed list
- Server role restriction — Operation requires SERVERADMIN but user is SERVERUSER
- Workspace restriction — Project is in a workspace where user has no membership
Diagnostic steps:
# Step 1: Check what scopes the server supports
query { serverInfo { scopes { name description } } }
# Step 2: Check your role on a specific project
query { project(id: "") { role } }
# Step 3: Check granular permissions on a project
query {
project(id: "") {
permissions {
canLoad { authorized code message }
canCreateModel { authorized code message }
canDelete { authorized code message }
canPublish { authorized code message }
}
}
}
Personal Access Tokens (PATs)
Common PAT Errors
Error: "UNAUTHENTICATED" with a PAT that previously worked
Symptom: A PAT that was working returns UNAUTHENTICATED.
Cause: PAT was revoked (by user or admin), or the Speckle server was reinstalled/migrated and tokens were invalidated.
Fix:
- Go to Profile > Settings > Developer > Access Tokens
- Check if the token still exists in the list
- If not, generate a new token with the required scopes
- Update the token in your environment variables or secret manager
Error: "FORBIDDEN" on streams:write operation with a PAT
Symptom: Token authenticates successfully but write operations fail.
Cause: PAT was created with only streams:read scope.
Fix:
- Check the scopes assigned to your token (you cannot inspect scopes of an existing PAT after creation)
- ALWAYS create a new PAT with the correct scopes — you CANNOT modify scopes of an existing token
- Revoke the old token
Error: Operations work on some projects but not others
Symptom: Same token works for Project A but returns FORBIDDEN for Project B.
Cause: Token was created with limitResources restricting it to specific projects or workspaces.
Fix:
- Create a new token without resource restrictions, OR
- Add the target project/workspace to the allowed resources when creating the token
mutation {
apiTokenCreate(token: {
name: "My Scoped Token"
scopes: ["streams:read", "streams:write", "profile:read"]
lifespan: 7776000
limitResources: [
{ id: "", type: project }
{ id: "", type: workspace }
]
})
}
PAT Security Rules
- NEVER embed PATs in client-side JavaScript — use OAuth2 flow for browser apps
- NEVER commit tokens to version control — use environment variables
- NEVER create tokens with more scopes than needed — principle of least privilege
- ALWAYS set a
lifespan(in seconds) when creating tokens programmatically - ALWAYS rotate tokens periodically and revoke unused tokens
- ALWAYS revoke tokens immediately if compromised (see Token Compromise Response below)
OAuth2 Flow Errors
Speckle implements OAuth2 Authorization Code flow with a challenge parameter (similar to PKCE).
Challenge Mismatch Error
Symptom: Token exchange (POST /auth/token) returns an error after successful user authorization.
Cause: The challenge string sent in Step 4 (token exchange) does not match the one used in Step 2 (authorization redirect).
Fix:
- Generate a cryptographically random challenge string ONCE
- Store it in
localStorageor session storage BEFORE redirecting - Retrieve the SAME string when exchanging the access code
- NEVER regenerate the challenge between redirect and token exchange
// CORRECT: Store challenge before redirect
const challenge = crypto.randomUUID();
localStorage.setItem('speckle_challenge', challenge);
window.location.href = `https://server/authn/verify/${appId}/${challenge}`;
// In callback handler:
const challenge = localStorage.getItem('speckle_challenge');
const response = await fetch('https://server/auth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
accessCode: receivedCode,
appId: APP_ID,
appSecret: APP_SECRET,
challenge: challenge // MUST match
})
});
Redirect URI Mismatch
Symptom: User sees an error page during authorization instead of being redirected back.
Cause: The redirect URI in the authorization request does not exactly match the URI registered for the application.
Fix:
- The redirect URI MUST be an exact string match (including trailing slashes, port numbers, protocol)
http://localhost:3000/callbackdoes NOT matchhttp://localhost:3000/callback/- Register separate applications for development (
localhost) and production environments
Expired Access Code
Symptom: Token exchange fails even though the challenge matches.
Cause: The access code received in the callback has a short validity window.
Fix: Exchange the access code for tokens immediately in the callback handler. NEVER store access codes for later use.
App Secret in Client-Side Code
Symptom: Security vulnerability, not a runtime error.
Cause: App Secret embedded in frontend JavaScript is visible to anyone inspecting the page.
Fix:
- NEVER include App Secret in client-side code
- Use a backend proxy to handle token exchange
- For pure frontend apps, use the
speckle-authcommunity package which handles public client flow
Server vs Cloud Authentication
Endpoint Differences
| Aspect | Speckle Cloud | Self-Hosted Server | |--------|--------------|-------------------| | GraphQL endpoint | https://app.speckle.systems/graphql | https:///graphql | | Auth redirect | https://app.speckle.systems/authn/verify/... | https:///authn/verify/... | | Token exchange | https://app.speckle.systems/auth/token | https:///auth/token | | PAT creation | Cloud UI: Settings > Developer | Server UI: Settings > Developer |
Common Cross-Server Errors
Error: Token works in browser but not in script
Symptom: User can access the Speckle frontend but API calls with their token fail.
Cause: Token was generated on a different server instance. Common when users have both cloud and self-hosted accounts.
Fix:
- Verify which server the token belongs to
- Use
serverInfoquery (no auth required) to confirm you are hitting the correct server:
``graphql query { serverInfo { name canonicalUrl version } } ``
- Generate a token on the correct server
Error: SSO login works but API token does not
Symptom: User authenticates via SSO (Azure AD, Google, GitHub) in browser but PAT fails.
Cause: On self-hosted servers with SSO, user accounts are created through the SSO provider. PATs are still generated through the Speckle UI and are independent of SSO session tokens. If the SSO-created account is deactivated or the server enforces re-authentication, existing PATs may be invalidated.
Fix:
- Verify the user account is still active on the server
- Generate a new PAT through the Speckle UI (after SSO login)
- Check with the server admin if token-based access is restricted
Error: Self-hosted server returns unexpected auth strategy
Symptom: OAuth flow fails because the server uses a different authentication strategy.
Cause: Self-hosted servers can configure custom auth strategies (local, Azure AD, Google, GitHub, OIDC).
Fix:
# Discover available auth strategies
query { serverInfo { authStrategies { id name icon } } }
Use the returned strategies to determine the correct authentication flow.
Refresh Token Errors
Refresh Token Expired
Symptom: POST /auth/token with refresh token returns an error.
Cause: Refresh tokens have a limited lifetime. When expired, no programmatic renewal is possible.
Fix: The user MUST re-authenticate through the full OAuth2 flow (browser redirect).
Refresh Token Already Used
Symptom: Refresh succeeds once but subsequent refreshes with the same token fail.
Cause: Speckle refresh tokens are single-use. Each refresh returns a NEW access token AND a NEW refresh token.
Fix:
- ALWAYS store the new refresh token returned from each refresh operation
- NEVER reuse an old refresh token — it is invalidated after first use
// CORRECT: Always update stored refresh token
const response = await fetch('https://server/auth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
refreshToken: storedRefreshToken,
appId: APP_ID,
appSecret: APP_SECRET
})
});
const { token, refreshToken } = await response.json();
// MUST store the new refreshToken for next refresh cycle
saveToken(token);
saveRefreshToken(refreshToken); // CRITICAL: save the NEW refresh token
Token Compromise Response
If a token (PAT or OAuth) is compromised:
- Immediately revoke the token: Settings > Developer > Access Tokens
- Generate a replacement token with the same scopes
- Update all applications using the compromised token
- Review server access logs for unauthorized activity
- If an App Secret is exposed: regenerate the app secret and update all deployments
SpecklePy Authentication Errors
Common SpecklePy Auth Patterns
from specklepy.api.client import SpeckleClient
from specklepy.api.credentials import get_default_account
# Pattern 1: Authenticate with PAT
client = SpeckleClient(host="https://app.speckle.systems")
client.authenticate_with_token("your_token_here")
# Pattern 2: Use stored account (from Speckle Manager)
account = get_default_account()
client = SpeckleClient(host=account.serverInfo.url)
client.authenticate_with_account(account)
Error: "SpeckleException: Authentication failed"
Cause: Token is invalid, expired, or for a different server.
Fix:
- Verify the token is for the correct server URL
- Test the token with a simple
activeUserquery - If using
get_default_account(), ensure Speckle Manager has an active account configured
Error: "SpeckleException: No default account found"
Cause: No account is configured in Speckle Manager, or the accounts database is corrupted.
Fix:
- Open Speckle Manager and log in to your server
- Alternatively, authenticate directly with a token instead of using
get_default_account()
GraphQL Authorization Directives
Speckle uses custom GraphQL directives for fine-grained authorization:
| Directive | Purpose | Failure Code | |-----------|---------|-------------| | @hasServerRole(role: SERVER_USER) | Requires minimum server role | FORBIDDEN | | @hasScope(scope: "streams:read") | Requires token to have specific scope | FORBIDDEN | | @hasScopes(scopes: [...]) | Requires multiple scopes | FORBIDDEN | | @isOwner | Restricts to resource owner | FORBIDDEN |
When a directive check fails, the GraphQL response contains:
{
"errors": [
{
"message": "You do not have the required server role",
"extensions": { "code": "FORBIDDEN" }
}
],
"data": null
}
ALWAYS check the extensions.code field to distinguish between FORBIDDEN and UNAUTHENTICATED.
Reference Links
- [references/methods.md](references/methods.md) -- Auth-related API methods, token creation, scope verification
- [references/examples.md](references/examples.md) -- Working auth code examples for PAT, OAuth2, and token refresh
- [references/anti-patterns.md]
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: Impertio-Studio
- Source: Impertio-Studio/Speckle-Claude-Skill-Package
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.