Authentication Architecture
OpenPrime uses Keycloak for authentication, implementing OpenID Connect (OIDC) with PKCE flow.
Authentication Flowβ
ββββββββββ ββββββββββββββ ββββββββββββ βββββββββββ
β Browserβ β Frontend β β Keycloak β β Backend β
βββββ¬βββββ βββββββ¬βββββββ ββββββ¬ββββββ ββββββ¬βββββ
β β β β
β 1. Visit App β β β
βββββββββββββββββββΆβ β β
β β β β
β β 2. Check Auth β β
β ββββββββββββββββββββΆβ β
β β β β
β 3. Redirect to Login β β
ββββββββββββββββββββββββββββββββββββββββ β
β β β β
β 4. User Login β β β
βββββββββββββββββββββββββββββββββββββββΆβ β
β β β β
β 5. Auth Code + Redirect β β
ββββββββββββββββββββββββββββββββββββββββ β
β β β β
β β 6. Exchange Code β β
β ββββββββββββββββββββΆβ β
β β β β
β β 7. JWT Tokens β β
β βββββββββββββββββββββ β
β β β β
β β 8. API Request (Bearer Token) β
β βββββββββββββββββββββββββββββββββββββββΆβ
β β β β
β β β 9. Validate JWT β
β β ββββββββββββββββββββ
β β β β
β β 10. Response β β
β ββββββββββββββββββββββββββββββββββββββββ
β β β β
Componentsβ
Frontend (keycloak-js)β
// Keycloak initialization
import Keycloak from 'keycloak-js';
const keycloak = new Keycloak({
url: 'http://localhost:8080',
realm: 'openprime',
clientId: 'openprime-app'
});
// Initialize with PKCE
await keycloak.init({
onLoad: 'check-sso',
pkceMethod: 'S256',
checkLoginIframe: false
});
Backend (keycloak-connect)β
// JWT validation middleware
const Keycloak = require('keycloak-connect');
const keycloak = new Keycloak({}, {
realm: 'openprime',
'auth-server-url': 'http://localhost:8080',
'ssl-required': 'external',
resource: 'openprime-backend',
'bearer-only': true
});
app.use(keycloak.middleware());
app.use('/api', keycloak.protect());
Keycloak Configurationβ
Realm: openprimeβ
{
"realm": "openprime",
"enabled": true,
"sslRequired": "external",
"registrationAllowed": true,
"loginWithEmailAllowed": true,
"duplicateEmailsAllowed": false,
"resetPasswordAllowed": true,
"editUsernameAllowed": false,
"bruteForceProtected": true
}
Client: openprime-app (Frontend)β
{
"clientId": "openprime-app",
"enabled": true,
"publicClient": true,
"standardFlowEnabled": true,
"directAccessGrantsEnabled": false,
"rootUrl": "http://localhost:3000",
"redirectUris": ["http://localhost:3000/*"],
"webOrigins": ["http://localhost:3000"],
"protocol": "openid-connect"
}
Client: openprime-backend (API)β
{
"clientId": "openprime-backend",
"enabled": true,
"bearerOnly": true,
"publicClient": false
}
Token Structureβ
Access Token Claimsβ
{
"exp": 1699999999,
"iat": 1699996399,
"jti": "unique-token-id",
"iss": "http://localhost:8080/realms/openprime",
"sub": "user-uuid",
"typ": "Bearer",
"azp": "openprime-app",
"preferred_username": "john.doe",
"email": "john.doe@example.com",
"email_verified": true,
"realm_access": {
"roles": ["user"]
}
}
User Provisioningβ
Users are automatically created in OpenPrime database on first authenticated request:
// Backend middleware
async function ensureUserExists(req, res, next) {
const keycloakId = req.kauth.grant.access_token.content.sub;
const username = req.kauth.grant.access_token.content.preferred_username;
const email = req.kauth.grant.access_token.content.email;
let user = await User.findOne({ where: { keycloakId } });
if (!user) {
user = await User.create({
keycloakId,
username,
email
});
}
req.user = user;
next();
}
Token Refreshβ
The frontend automatically refreshes tokens before expiry:
// Auto-refresh configuration
keycloak.onTokenExpired = () => {
keycloak.updateToken(30)
.then(refreshed => {
if (refreshed) {
console.log('Token refreshed');
}
})
.catch(() => {
console.error('Failed to refresh token');
keycloak.login();
});
};
Session Managementβ
Logoutβ
// Frontend logout
keycloak.logout({
redirectUri: window.location.origin
});
Silent Check SSOβ
// Check if user is authenticated without prompting
keycloak.init({
onLoad: 'check-sso',
silentCheckSsoRedirectUri:
window.location.origin + '/silent-check-sso.html'
});
Security Considerationsβ
PKCE (Proof Key for Code Exchange)β
- Prevents authorization code interception attacks
- Required for public clients (SPAs)
- Uses SHA-256 code challenge method
Token Storageβ
- Access tokens stored in memory (not localStorage)
- Refresh tokens handled by Keycloak JS
- No sensitive data in browser storage
CORS Configurationβ
// Backend CORS settings
app.use(cors({
origin: ['http://localhost:3000'],
credentials: true
}));
Rate Limitingβ
// Limit authentication attempts
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
});
Identity Federationβ
Keycloak supports external identity providers:
- Social: Google, GitHub, Microsoft
- Enterprise: SAML 2.0, LDAP, Active Directory
- Custom: OpenID Connect providers
Configure in Keycloak Admin β Identity Providers.