Authentication¶
Dxtra uses JWT-based authentication for secure API access. This guide covers personal access tokens and the JWT token exchange flow.
Personal Access Tokens¶
Personal Access Tokens (PATs) provide programmatic access to the GraphQL API. These are refresh tokens that can be exchanged for short-lived JWT access tokens.
What is a Personal Access Token?
Personal Access Tokens are long-lived refresh tokens. They authenticate your application and can be exchanged for JWT tokens to make API calls.
Generating a Personal Access Token¶
One-Time Display
Personal Access Tokens are shown only once during generation. Store your token securely immediately.
In the Dxtra Dashboard, navigate to Developers in the top bar. This page displays your existing tokens and allows you to create new ones.
Click "Create API Key" and provide a descriptive name (e.g., "Production API", "CI/CD Pipeline"). The system automatically sets an expiration date based on the token lifecycle policy.
Copy the token immediately after creation.
Security Critical
- Copy immediately -- The token is only displayed once
- Store securely -- Use environment variables or secrets management
- Never commit to source control -- Add to
.gitignore
Using Your Personal Access Token¶
Personal Access Tokens must be exchanged for JWT tokens before making API calls.
sequenceDiagram
participant App as Your Application
participant Auth as auth.dxtra.ai
participant API as api.dxtra.ai
App->>Auth: POST /v1/signin/pat
Note over App,Auth: {"personalAccessToken": "PAT"}
Auth-->>App: JWT + Refresh Token
Note over Auth,App: {"session": {"accessToken": "JWT...", "refreshToken": "..."}}
App->>API: GraphQL Query + JWT
Note over App,API: Authorization: Bearer JWT<br/>X-Hasura-Role: user
API-->>App: GraphQL Response
alt Token Expired (401)
API-->>App: 401 Unauthorized
App->>Auth: POST /v1/signin/pat
Auth-->>App: New JWT Token
App->>API: Retry with New JWT
API-->>App: Successful Response
end API Endpoints:
| Environment | Authentication | GraphQL API |
|---|---|---|
| Production | https://auth.dxtra.ai | https://api.dxtra.ai |
curl \
-X POST \
-H "Content-Type: application/json" \
--data '{"personalAccessToken": "YOUR_PAT"}' \
https://auth.dxtra.ai/v1/signin/pat
Response:
{
"session": {
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"accessTokenExpiresIn": 3600,
"refreshToken": "abc123...",
"user": {
"id": "user-uuid",
"email": "user@example.com",
"defaultRole": "dataSubject"
}
}
}
- accessToken -- JWT access token, valid for 1 hour
- accessTokenExpiresIn -- Token lifetime in seconds (3600 = 1 hour)
- refreshToken -- Use to get new access tokens, valid for 12 hours
Available Roles
| Role | Access Level | Description |
|---|---|---|
anonymous | Public | Access for public transparency widgets, consent tracking, and event tracking |
dataSubject | Default | Access for privacy transparency center users |
me | User-scoped | Access scoped to the authenticated user's own data |
user | Standard | Access to data controllers and management features |
Default role for new users: dataSubject
JWT Authentication (Client Applications)¶
For web applications and the Dxtra Dashboard, authentication uses JWT tokens.
const authenticateWithPAT = async (personalAccessToken) => {
const response = await fetch('https://auth.dxtra.ai/v1/signin/pat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ personalAccessToken })
});
if (!response.ok) {
throw new Error(`Authentication failed: ${response.statusText}`);
}
const data = await response.json();
return {
accessToken: data.session.accessToken,
refreshToken: data.session.refreshToken
};
};
import requests
def authenticate_with_pat(personal_access_token: str) -> dict:
response = requests.post(
'https://auth.dxtra.ai/v1/signin/pat',
json={'personalAccessToken': personal_access_token}
)
response.raise_for_status()
session = response.json()['session']
return {
'access_token': session['accessToken'],
'refresh_token': session['refreshToken']
}
const makeGraphQLCall = async (jwtToken, query, role = 'user') => {
const response = await fetch('https://api.dxtra.ai/v1/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${jwtToken}`,
'X-Hasura-Role': role
},
body: JSON.stringify({ query })
});
const result = await response.json();
if (result.errors) {
throw new Error(`GraphQL Error: ${result.errors[0].message}`);
}
return result.data;
};
- Production GraphQL endpoint (Hasura)
- JWT Bearer token authentication
- Hasura role for permission checks
- Standard GraphQL query format
- Check for GraphQL errors in response
- Return data object
import { NhostProvider, useAuthenticated } from '@nhost/react';
import { NhostClient } from '@nhost/nhost-js';
// Initialize Nhost client with custom domain
const nhost = new NhostClient({
authUrl: 'https://auth.dxtra.ai',
graphqlUrl: 'https://api.dxtra.ai/v1/graphql',
storageUrl: 'https://storage.dxtra.ai',
});
function App() {
return (
<NhostProvider nhost={nhost}>
<AuthenticatedApp />
</NhostProvider>
);
}
function AuthenticatedApp() {
const isAuthenticated = useAuthenticated();
if (!isAuthenticated) {
return <LoginPage />;
}
return <Dashboard />;
}
Token Management¶
JWT Token Lifecycle¶
| Token Type | Duration | Purpose |
|---|---|---|
| Access Token (JWT) | 1 hour | API calls and GraphQL queries |
| Refresh Token | 12 hours | Obtain new access tokens |
| Personal Access Token | Variable | Initial authentication (managed in Dashboard) |
When a token expires, you receive a 401 Unauthorized response. Re-authenticate with your Personal Access Token to get a new JWT.
Token Refresh Example¶
const makeAuthenticatedRequest = async (personalAccessToken, query) => {
let { accessToken } = await authenticateWithPAT(personalAccessToken);
try {
return await makeGraphQLCall(accessToken, query);
} catch (error) {
if (error.status === 401) {
const { accessToken: newToken } = await authenticateWithPAT(personalAccessToken);
return await makeGraphQLCall(newToken, query);
}
throw error;
}
};
Proactive Token Refresh¶
For long-running applications, implement proactive token refresh:
class AuthManager {
constructor(personalAccessToken) {
this.pat = personalAccessToken;
this.accessToken = null;
this.refreshAt = null;
}
async getAccessToken() {
const now = Date.now();
if (!this.accessToken || now >= this.refreshAt) {
const { accessToken, accessTokenExpiresIn } =
await authenticateWithPAT(this.pat);
this.accessToken = accessToken;
// Refresh 2 minutes before expiration
this.refreshAt = now + ((accessTokenExpiresIn - 120) * 1000);
}
return this.accessToken;
}
}
Security Best Practices¶
Critical Security Guidelines
Personal Access Token Management¶
- Keep tokens secret -- Never share or commit to version control
- Use environment variables -- Store in
.envfiles (add to.gitignore) - Rotate compromised tokens -- Delete and regenerate immediately if exposed
- Use separate tokens per environment -- Different tokens for dev/staging/production
Access Control¶
- Use appropriate roles -- Grant minimum necessary permissions
- Validate user roles -- Check
X-Hasura-Rolematches expected value - Implement rate limiting -- Protect against abuse (see Rate Limits)
Token Handling¶
- Handle expiration gracefully -- Implement automatic refresh logic
- Store JWTs securely -- Memory only, never localStorage for sensitive apps
- Use HTTPS exclusively -- All API calls must use secure transport
Environment Variables
Store sensitive credentials using environment variables:
Technical Details¶
Security Features¶
| Feature | Status | Details |
|---|---|---|
| Multi-Factor Authentication | Supported | TOTP-based MFA |
| WebAuthn | Supported | Passwordless hardware key authentication |
| Email Passwordless | Supported | Magic link authentication |
| GitHub OAuth | Supported | Social login integration |
| Google OAuth | Supported | Social login integration |
| Rate Limiting | Enabled | See Rate Limits for details |
| Audit Logging | Enabled | All authentication events logged |
Troubleshooting¶
Common Issues¶
401 Unauthorized Error
Cause: JWT token expired or invalid
Solution: Re-authenticate with your Personal Access Token to get a new JWT
403 Forbidden Error
Cause: JWT valid but role lacks permission for requested operation
Solution: Verify you are using the correct X-Hasura-Role header value
Available roles: anonymous, dataSubject, me, user
Invalid Token Format
Cause: Malformed JWT or missing Bearer prefix
Solution: Ensure Authorization header format: Bearer <token>
Getting Help¶
If you encounter authentication issues:
- Check token expiration -- JWTs expire after 1 hour
- Verify endpoints -- Ensure using correct environment URLs
- Review role permissions -- Confirm role has required access
- Contact support -- Email privacy@dxtra.ai with error details
Next Steps¶
- GraphQL Reference -- Explore available queries and mutations
- Integrations -- Platform-specific implementation examples
- Error Handling -- Comprehensive error code reference