Molecule provides a robust authorization framework (role-based access control) but intentionally does NOT include authentication (credential validation). This separation follows security best practices and allows you to integrate with any authentication system of your choice.
- Role-based access control at entity and attribute level
- Action-level permissions (query, save, update, delete)
- Runtime authorization checks on all database operations
- Tested and production-ready authorization framework
- Credential validation (username/password, tokens, etc.)
- User identity verification before creating
AuthContext - Session management and token generation
- Production-ready authentication implementation
For testing only, Molecule provides a convenient withAuth() method:
// JVM Testing
val adminConn = baseConn.withAuth("user1", "Admin")
Person.name.query.get(using adminConn)
// JS Testing (with test server)
for {
adminConn <- baseConn.withAuth("user1", "Admin")
names <- Person.name.query.get(using adminConn)
} yield names- This method is
private[molecule]- not accessible in your application code - It bypasses all credential validation
- It is ONLY for testing authorization rules
- The
/authendpoint must be removed from production servers
For production, you must implement secure authentication:
// 1. Validate credentials
def login(username: String, password: String): Future[Option[User]] = {
// Validate against your user database
// Hash passwords with bcrypt/argon2
// Return user with roles if valid
}
// 2. Generate secure token (JWT recommended)
def generateToken(user: User): String = {
// Create signed JWT with user claims
// Set appropriate expiration
}
// 3. Validate token on each request
def validateAndCreateAuthContext(token: String): Option[AuthContext] = {
verifyJWT(token) match {
case Valid(claims) =>
Some(AuthContext(claims.userId, claims.role, claims.data))
case Invalid(_) =>
None
}
}See ProductionAuthExample.scala for complete implementation examples.
┌─────────────────────────────────────────────────────┐
│ Authentication (YOUR RESPONSIBILITY) │
│ - Validate credentials │
│ - Generate secure tokens │
│ - Verify token signatures │
│ - Create AuthContext with validated claims │
└────────────────┬────────────────────────────────────┘
│ AuthContext(userId, role, data)
▼
┌─────────────────────────────────────────────────────┐
│ Authorization (MOLECULE HANDLES) │
│ - Check role has permission for entity │
│ - Validate action allowed (query/save/update/delete)│
│ - Enforce attribute-level access control │
│ - Return data or throw access denied error │
└─────────────────────────────────────────────────────┘
// Create connection with authentication
val authCtx = validateUserCredentials(token) // Your implementation
val conn = JdbcConn_JVM(proxy, sqlConn, Some(authCtx))
// Authorization happens automatically
Person.name.age.query.get(using conn) // ✓ Authorized
Admin.secretData.query.get(using conn) // ✗ Access denied// Client sends token, server validates and creates AuthContext
val conn = JdbcConn_JS(proxy, "api.example.com", 443, "https")
// Server-side: Validate token and populate authCache
authCache.put(conn.uuid, validatedAuthContext)
// Authorization happens server-side
Person.name.age.query.get(using conn) // ✓ AuthorizedThe /auth endpoint is automatically disabled in production by default. It's only enabled when you explicitly set an environment variable or system property.
✅ Safe by Default: Just don't enable test endpoints!
- DO NOT set
MOLECULE_ENABLE_TEST_ENDPOINTS=truein production - DO NOT set
-Dmolecule.enable.test.endpoints=truein production JVM args - Verify
/authreturns 404 in production by testing:curl http://your-server/molecule/auth - Verify
withAuth()is not accessible (it'sprivate[molecule])
For Testing/Development:
# Enable test endpoints in development
export MOLECULE_ENABLE_TEST_ENDPOINTS=true
# Or via JVM system property
sbt -Dmolecule.enable.test.endpoints=true runIn Production: Simply don't set these flags. The endpoint won't be registered.
- Choose authentication method (JWT, OAuth2, sessions)
- Implement credential validation
- Generate secure tokens with appropriate expiration
- Validate tokens on every request
- Create
AuthContextonly from validated credentials
- Use HTTPS only (TLS 1.2+)
- Set secure cookie flags (HttpOnly, Secure, SameSite)
- Implement CORS with strict origin whitelist
- Add CSRF protection
- Use secure headers (CSP, HSTS, X-Frame-Options)
- Use strong secret keys (256+ bits random)
- Rotate keys periodically
- Store sessions in secure backend (Redis, database)
- Implement session timeout and renewal
- Clear sessions on logout
- Hash passwords with bcrypt or argon2
- Never store passwords in plaintext
- Implement password complexity requirements
- Add rate limiting on login attempts
- Lock accounts after failed attempts
- Log authentication failures
- Monitor for brute force attempts
- Set up intrusion detection
- Alert on suspicious patterns
Pros: Stateless, scalable, industry standard Cons: Cannot revoke before expiry
import pdi.jwt.{Jwt, JwtAlgorithm, JwtClaim}
// Generate token on login
val claim = JwtClaim(
content = s"""{"userId":"$userId","role":"$role"}""",
expiration = Some(System.currentTimeMillis() / 1000 + 3600)
)
val token = Jwt.encode(claim, secretKey, JwtAlgorithm.HS256)
// Validate token on each request
Jwt.decode(token, secretKey, Seq(JwtAlgorithm.HS256)) match {
case Success(claims) =>
val authCtx = AuthContext(claims.userId, claims.role)
case Failure(_) =>
// Invalid token, deny access
}Pros: Easy to implement, can revoke sessions Cons: Requires session storage, less scalable
// Generate secure session ID
val sessionId = java.util.UUID.randomUUID().toString
// Store in Redis/database
sessionStore.put(sessionId, UserSession(userId, role, expiresAt))
// Set secure cookie
setCookie("sessionId", sessionId, HttpOnly = true, Secure = true)
// Validate on each request
sessionStore.get(sessionId) match {
case Some(session) if !session.isExpired =>
val authCtx = AuthContext(session.userId, session.role)
case _ =>
// Invalid or expired session
}Pros: Delegates auth, SSO support Cons: Complex setup, external dependency
// Use libraries like pac4j or scala-oauth2-provider
// Validate OAuth token with identity provider
// Map OAuth claims to Molecule rolesPros: Simple for machine-to-machine Cons: Less secure if keys leak
// Generate cryptographically random key
val apiKey = SecureRandom.getInstanceStrong()
.ints(32, 33, 127)
.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
.toString()
// Hash before storing (like passwords)
val hashedKey = BCrypt.hashpw(apiKey, BCrypt.gensalt())
// Validate on each request
if (BCrypt.checkpw(providedKey, storedHashedKey)) {
val authCtx = AuthContext(userId, role)
}A: Authentication is highly application-specific. By separating concerns:
- You can use any authentication system (JWT, OAuth, LDAP, custom)
- Molecule focuses on what it does best: authorization
- You maintain full control over security policies
- No vendor lock-in to a specific auth system
A: Yes! The authorization framework is:
- ✅ Tested across all database platforms
- ✅ Enforced at runtime on all operations
- ✅ Cannot be bypassed by client code
- ✅ Uses bitmask-based permissions for efficiency
- ✅ Provides both strict and permissive modes
A: NO! The withAuth() method is:
- ❌ Package-private (you can't access it anyway)
- ❌ Bypasses credential validation
- ❌ Only for testing authorization rules
- ❌ Would allow privilege escalation
A: Without authentication, all operations run as unauthenticated:
- Only public entities are accessible
- Entities requiring roles will return "Access denied"
- This is safe by default (principle of least privilege)
A: Use the withAuth() method in your test suites:
"Admin can access sensitive data" - testDb {
for {
adminConn <- baseConn.withAuth("user1", "Admin")
_ <- SensitiveData.info.save.transact(using adminConn)
data <- SensitiveData.info.query.get(using adminConn)
} yield {
data ==> List("sensitive info")
}
}
"Guest cannot access sensitive data" - testDb {
for {
adminConn <- baseConn.withAuth("user1", "Admin")
_ <- SensitiveData.info.save.transact(using adminConn)
guestConn <- baseConn.withAuth("user2", "Guest")
result <- SensitiveData.info.query.get(using guestConn)
.recover { case ModelError(err) => err }
} yield {
result ==> "Access denied: Role 'Guest' cannot query entity 'SensitiveData'"
}
}- Production Auth Example:
ProductionAuthExample.scala - Authorization Setup: See Molecule documentation on defining roles and permissions
- JWT Libraries: jwt-scala, jose4j, java-jwt
- OAuth Libraries: pac4j, scala-oauth2-provider
- Password Hashing: bcrypt, argon2
If you discover a security vulnerability in Molecule's authorization framework, please email [email protected] or create a private security advisory on GitHub.
DO NOT create public issues for security vulnerabilities.
This security documentation is part of the Molecule project and is provided as-is for informational purposes.