@atlanhq/atlan-auth
v1.0.0
Published
Official Atlan Authentication SDK for seamless external app integration
Downloads
56
Readme
@atlanhq/atlan-auth
Official Authentication SDK for integrating external applications with Atlan, featuring seamless authentication via Keycloak.
Features
- 🔐 Automatic Authentication: Auto-detects embedded vs standalone mode
- 🎫 Keycloak Integration: Uses Keycloak with PKCE for secure, browser-safe auth
- 🔄 Auto Token Refresh: Tokens refresh automatically before expiration
- 🎯 Type-Safe: Full TypeScript support
- 📦 Small Bundle: ~52KB gzipped (includes Keycloak)
- 🚀 Zero Config: Just provide your Atlan origin URL
Installation
CDN (For plain HTML/JS apps)
<script src="https://unpkg.com/@atlanhq/atlan-auth@latest/dist/atlan-auth.umd.min.js"></script>
<script>
const { AtlanAuth } = window.AtlanAuthSDK
// SDK ready to use!
</script>npm (For React/Vue/Angular apps)
# Add to .npmrc:
# @atlanhq:registry=https://npm.pkg.github.com
npm install @atlanhq/atlan-auth keycloak-js
# or
yarn add @atlanhq/atlan-auth keycloak-js
# or
pnpm add @atlanhq/atlan-auth keycloak-jsNote: When using npm, you need to install
keycloak-jsas a peer dependency. CDN users get it bundled automatically.
Quick Start
The SDK works in two modes automatically:
- Embedded Mode: When your app runs inside an Atlan iframe (production)
- Standalone Mode: When developing locally or running outside Atlan
Embedded Mode (Inside Atlan iframe)
const atlan = new AtlanAuth({
origin: 'https://your-tenant.atlan.com',
})
await atlan.init()
// ✅ Instant authentication via postMessage
console.log('User:', atlan.getUser())
console.log('Token:', atlan.getToken())Standalone Mode (Local development)
const atlan = new AtlanAuth({
origin: 'http://localhost:3333', // Your local Atlan instance
})
await atlan.init()
// 🔄 Redirects to Atlan login page
// ✅ Returns after successful authentication
console.log('User:', atlan.getUser())What happens in standalone mode:
- SDK detects you're not in an iframe
- Silently checks for existing session (no redirect if already logged in!)
- If no session: Redirects to
{origin}/auth/realms/default/login - You log in with your Atlan credentials
- Keycloak redirects back to your app with auth code
- SDK exchanges code for token (PKCE flow)
- Your app is now authenticated!
Note: The SDK uses "silent SSO check" by default, so returning users won't see a redirect flash!
Complete Example
// CDN usage
const { AtlanAuth } = window.AtlanAuthSDK
// npm usage: import { AtlanAuth } from '@atlanhq/atlan-auth'
const atlan = new AtlanAuth({
origin: 'https://jpmc.atlan.com',
onReady: (context) => {
console.log('✅ SDK Ready!')
console.log('Mode:', context.mode) // 'embedded' or 'standalone'
console.log('User:', context.user)
console.log('Tenant:', context.tenant)
},
onError: (error) => {
console.error('❌ SDK Error:', error)
},
debug: true, // Enable console logging
})
// Initialize (async)
try {
await atlan.init()
// Make API calls
const assets = await atlan.api.get('/api/meta/search')
console.log('Assets:', assets.data)
} catch (error) {
console.error('Initialization failed:', error)
}Handling Loading States
To prevent a flash of content before authentication, use CSS-first loading patterns:
Recommended Pattern (CSS-First)
<head>
<style>
/* Hide app until authenticated */
body.loading #app {
display: none;
}
body.loading::before {
content: '🔐 Authenticating...';
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
</style>
</head>
<body class="loading">
<div id="app">
<!-- Your app content -->
</div>
<script src="https://unpkg.com/@atlanhq/atlan-auth@latest/dist/atlan-auth.umd.min.js"></script>
<script>
const { AtlanAuth } = window.AtlanAuthSDK
const atlan = new AtlanAuth({
origin: 'https://your-tenant.atlan.com',
onReady: (context) => {
// Remove loading state
document.body.classList.remove('loading')
},
})
atlan.init()
</script>
</body>Why CSS-first? CSS loads before JavaScript, so the loading state is visible from the first paint—no flash!
Alternative: Using Callbacks
const atlan = new AtlanAuth({
origin: 'https://your-tenant.atlan.com',
onStateChange: (state) => {
if (state === 'initializing') {
showLoader()
} else if (state === 'authenticated') {
hideLoader()
}
},
})
await atlan.init()Note: In standalone mode, the SDK uses "silent SSO check"—returning users authenticate instantly without redirect!
API Reference
Constructor
new AtlanAuth(config: AtlanAuthConfig)Config Options:
interface AtlanAuthConfig {
origin: string // Atlan instance URL (required)
onReady?: (context: AuthContext) => void // Success callback
onError?: (error: AuthError) => void // Error callback
onStateChange?: (state: AuthState, prevState: AuthState) => void // State change callback
onTokenRefresh?: (token: string) => void // Token refresh callback
onTokenExpired?: () => void // Token expired callback
onLogout?: () => void // Logout callback
keycloak?: KeycloakConfig // Keycloak customization
interceptors?: Interceptors // Request/response interceptors
debug?: boolean // Enable debug logging (default: false)
}Methods
init(): Promise<AuthContext>
Initialize the SDK and authenticate. Must be called before using other methods.
const context = await atlan.init()Returns:
interface AuthContext {
token: string
user: User
tenant: string
mode: 'embedded' | 'standalone'
}getToken(): string | null
Get the current authentication token.
const token = atlan.getToken()getUser(): User | null
Get the current user information.
const user = atlan.getUser()
// { id, username, email, firstName, lastName }getTenant(): string | null
Get the current tenant name.
const tenant = atlan.getTenant()
// e.g., 'jpmc'getMode(): 'embedded' | 'standalone'
Get the current authentication mode.
const mode = atlan.getMode()isLoading(): boolean
Check if the SDK is currently loading or initializing. Safe to call before init().
const atlan = new AtlanAuth({ origin: '...' })
if (atlan.isLoading()) {
console.log('SDK is initializing...')
}Returns true if the SDK state is initializing or refreshing.
willRedirect(): boolean
Check if the SDK will redirect to the login page (standalone mode only). Safe to call before init().
const atlan = new AtlanAuth({ origin: '...' })
if (atlan.willRedirect()) {
// Show loading UI - redirect is about to happen
document.body.innerHTML = '<h2>Redirecting to login...</h2>'
}
await atlan.init() // Will redirect if willRedirect() returned trueReturns:
falsein embedded mode (never redirects)falsein standalone mode if returning from login (has auth code)truein standalone mode if no active session (will redirect)
Use case: Display appropriate loading UI before the redirect happens, preventing a flash of your app's content.
isInitialized(): boolean
Check if SDK is initialized.
if (atlan.isInitialized()) {
// SDK ready
}getState(): AuthState
Get the current authentication state.
const state = atlan.getState()
// 'idle' | 'initializing' | 'authenticated' | 'refreshing' | 'error' | 'logged_out'logout(options?): Promise<void>
Logout from the authentication provider.
await atlan.logout({ redirectUri: 'https://myapp.com' })retry(): Promise<AuthContext>
Retry initialization after an error.
try {
await atlan.init()
} catch (error) {
// Retry after error
await atlan.retry()
}reset(): void
Reset SDK to idle state.
atlan.reset()isTokenExpired(): boolean
Check if the current token is expired.
if (atlan.isTokenExpired()) {
await atlan.retry()
}getTokenExpiry(): Date | null
Get the token expiry date.
const expiry = atlan.getTokenExpiry()
console.log('Token expires at:', expiry)API Client
The SDK includes a built-in API client for making authenticated requests to Atlan:
api.get(url, options?)
const response = await atlan.api.get('/api/service/whoami')
console.log(response.data)api.post(url, data, options?)
const response = await atlan.api.post('/api/meta/entity', {
typeName: 'Table',
attributes: { name: 'my_table' },
})api.put(url, data, options?)
const response = await atlan.api.put('/api/meta/entity/guid/abc-123', {
attributes: { description: 'Updated' },
})api.delete(url, options?)
await atlan.api.delete('/api/meta/entity/guid/abc-123')API Response:
interface ApiResponse<T> {
data: T
status: number
headers: Record<string, string>
}Framework Examples
React
import { AtlanAuth } from '@atlanhq/atlan-auth'
import { useEffect, useState } from 'react'
function App() {
const [atlan, setAtlan] = useState(null)
const [user, setUser] = useState(null)
useEffect(() => {
const sdk = new AtlanAuth({
origin: 'https://your-tenant.atlan.com',
onReady: (context) => {
setUser(context.user)
},
onError: (error) => {
console.error(error)
},
})
sdk.init().then(() => setAtlan(sdk))
}, [])
if (!user) return <div>Loading...</div>
return (
<div>
<h1>Welcome, {user.username}!</h1>
</div>
)
}Vue 3
<template>
<div v-if="user">
<h1>Welcome, {{ user.username }}!</h1>
</div>
<div v-else>Loading...</div>
</template>
<script setup>
import { AtlanAuth } from '@atlanhq/atlan-auth'
import { ref, onMounted } from 'vue'
const atlan = ref(null)
const user = ref(null)
onMounted(async () => {
const sdk = new AtlanAuth({
origin: 'https://your-tenant.atlan.com',
onReady: (context) => {
user.value = context.user
},
})
await sdk.init()
atlan.value = sdk
})
</script>Plain HTML/JS
<!DOCTYPE html>
<html>
<head>
<title>My Atlan App</title>
</head>
<body>
<div id="app">Loading...</div>
<script src="https://unpkg.com/@atlanhq/atlan-auth@latest/dist/atlan-auth.umd.min.js"></script>
<script>
const { AtlanAuth } = window.AtlanAuthSDK
const atlan = new AtlanAuth({
origin: 'https://your-tenant.atlan.com',
onReady: (context) => {
document.getElementById('app').innerHTML = `
<h1>Welcome, ${context.user.username}!</h1>
`
},
})
atlan.init()
</script>
</body>
</html>How It Works
Embedded Mode (Production)
┌─────────────────────────────────────────┐
│ Atlan Frontend (localhost:3333) │
│ │
│ ┌─────────────────────────────────┐ │
│ │ Your App (in iframe) │ │
│ │ │ │
│ │ 1. SDK sends: IFRAME_READY ────┼───┤
│ │ │ │
│ │ 2. ────────── ATLAN_AUTH_CONTEXT │
│ │ (token, user via postMessage) ◄─┤
│ │ │ │
│ │ 3. ✅ Instant authentication │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────┘Standalone Mode (Development)
┌─────────────────────────────────────────┐
│ Your App (localhost:8000) │
│ │
│ 1. SDK detects standalone mode │
│ 2. Redirects to Keycloak login ────────┼─┐
└─────────────────────────────────────────┘ │
│
┌─────────────────────────────────────────┐ │
│ Atlan Keycloak (localhost:3333/auth) ◄─┘
│ │
│ 3. User logs in │
│ 4. Redirects back with auth code ──────┼─┐
└─────────────────────────────────────────┘ │
│
┌─────────────────────────────────────────┐ │
│ Your App (localhost:8000) ◄─┘
│ │
│ 5. SDK exchanges code for token (PKCE) │
│ 6. ✅ Authenticated │
└─────────────────────────────────────────┘Security
- ✅ No client secrets in browser: Uses Keycloak's PKCE flow (Authorization Code Flow with Proof Key for Code Exchange)
- ✅ Secure token storage: Tokens managed by Keycloak, not stored in localStorage
- ✅ Automatic refresh: Tokens refresh before expiration
- ✅ Origin validation: postMessage communication validates origins
Troubleshooting
Redirect loop in standalone mode
Make sure your Atlan instance is running and accessible at the origin URL.
"Not authenticated" errors
- Check that
atlan.init()completed successfully - Verify your Atlan instance URL is correct
- Enable debug logging:
debug: true
TypeScript errors
If using npm, ensure keycloak-js is installed:
npm install keycloak-jsCORS errors
CORS errors only occur in standalone mode. Make sure:
- Your Atlan instance allows your app's origin
- You're running your app on the same domain as your Atlan instance (e.g., both on
localhost)
License
ISC
Support
For issues and questions, please open an issue on GitHub.
