How do I use credentials, URLs, usernames and passwords or tokens to connect to databases or services?

← Security and Sanitization · Ref: Q706

EK9 separates configuration from secrets using EnvVars:

  env.get(name)            Non-secret config (URLs, hostnames, ports)
  env.sensitiveGet(name)   Secrets (passwords, API keys, tokens)

DATABASE CONNECTION PATTERN

  dbHost <- env.get("DB_HOST")
  dbPassword <- env.sensitiveGet("DB_PASSWORD")

Config as regular Strings, password as Sensitive (auto-redacts if logged).

DRIVER COMPONENT PATTERN

Driver uses Privileged trait to call reveal() at the connection boundary:

  DatabaseDriver with trait of Privileged
    connect()
      -> password as Sensitive
      rawPassword <- password.reveal()

Secrets flow as protected Sensitive values. Only Privileged components access raw values.

See Q701 for Sensitive type. See Q702 for sensitiveGet(). See Q703 for Privileged/reveal(). See Q112 for services. See Q271 for EnvVars. See Q231 for DI program-application linking. See Q232 for component chains.

Example

defines module qa.security.credentialpreparation

  defines trait

    <?-
      Trait for any component that consumes database credentials.
    -?>
    DatabaseConsumer
      connectToDatabase() as abstract
        ->
          host as String
          port as String
          dbName as String
          username as String
          password as Sensitive

  defines class

    <?-
      A database driver component with the Privileged trait.
      Only this class can call reveal() to access the raw password
      at the actual connection boundary.
    -?>
    DatabaseDriver with trait of Privileged, DatabaseConsumer

      override connectToDatabase()
        ->
          host as String
          port as String
          dbName as String
          username as String
          password as Sensitive

        stdout <- Stdout()

        //Configuration values are regular Strings - safe to log
        stdout.println(`Connecting to ${host}:${port}/${dbName} as ${username}`)

        //The password is Sensitive - auto-promotes to ***REDACTED***
        stdout.println(`Password status: ${password}`)

        //Only here, at the connection boundary, do we reveal the raw value
        rawPassword <- password.reveal()

        if rawPassword?
          //This is where the actual database driver call would go
          //e.g. connection <- jdbcDriver.connect(host, port, dbName, username, rawPassword)
          stdout.println("Database connection established")

      default operator ?

    <?-
      A service client component with the Privileged trait.
      Uses reveal() only at the HTTP request boundary.
    -?>
    ServiceClient with trait of Privileged

      callService()
        ->
          serviceUrl as String
          apiKey as Sensitive

        stdout <- Stdout()

        //URL is configuration - safe to log
        stdout.println(`Calling service at ${serviceUrl}`)

        //API key is Sensitive - auto-promotes to ***REDACTED***
        stdout.println(`API key status: ${apiKey}`)

        //Reveal only at the HTTP boundary
        rawKey <- apiKey.reveal()

        if rawKey?
          //This is where the actual HTTP client call would go
          //e.g. response <- httpClient.get(serviceUrl, "Authorization", "Bearer " + rawKey)
          stdout.println("Service call completed")

      default operator ?

  defines constant

    dbPasswordKey <- "DB_PASSWORD"

  defines function

    <?-
      Demonstrates the full database credential preparation pattern.
    -?>
    testDatabaseCredentials()
      stderr <- Stderr()
      env <- EnvVars()

      //Configuration values loaded with get() - regular Strings
      dbHost <- env.get("DB_HOST")
      dbPort <- env.get("DB_PORT")
      dbName <- env.get("DB_NAME")
      dbUser <- env.get("DB_USER")

      //Password loaded with sensitiveGet() - Sensitive type
      dbPassword <- env.sensitiveGet(dbPasswordKey)

      if dbHost? and dbPort? and dbName? and dbUser? and dbPassword?
        driver <- DatabaseDriver()
        if driver?
          driver.connectToDatabase(host: dbHost, port: dbPort, dbName: dbName, username: dbUser, password: dbPassword)
      else
        stderr.println("Database not fully configured - check environment variables")

    <?-
      Demonstrates the service API credential preparation pattern.
    -?>
    testServiceCredentials()
      stderr <- Stderr()
      env <- EnvVars()

      //Service URL is configuration
      serviceUrl <- env.get("PAYMENT_API_URL")

      //API key is a secret
      apiKey <- env.sensitiveGet("PAYMENT_API_KEY")

      if serviceUrl? and apiKey?
        client <- ServiceClient()
        if client?
          client.callService(serviceUrl, apiKey)
      else
        stderr.println("Payment service not configured - check environment variables")

    <?-
      Shows that credentials remain protected throughout the chain.
    -?>
    testCredentialProtection()
      stdout <- Stdout()
      env <- EnvVars()

      secret <- env.sensitiveGet(dbPasswordKey)

      if secret?
        //Safe everywhere - auto-promotes to ***REDACTED***
        stdout.println(`Direct: ${secret}`)
        msg <- "Credential: " + secret
        stdout.println(msg)

        //Can compare without revealing
        other <- env.sensitiveGet(dbPasswordKey)
        if other?
          if secret == other
            stdout.println("Credentials match (constant-time)")

  defines program

    CredentialPreparationDemo()
      stdout <- Stdout()
      stdout.println("Credential preparation for database and service connections")
      testDatabaseCredentials()
      testServiceCredentials()
      testCredentialProtection()

Common mistakes

E50060 — Database URLs with embedded passwords are detected at compile time. Separate host/port/name configuration from the password and load the password with sensitiveGet(). See ek9 -h E50060 for details.

Incorrect:

dbPassword <- "postgres://admin:s3cret@db.example.com/prod"

Correct:

dbPassword <- env.sensitiveGet(dbPasswordKey)

E11090 — The driver component needs the Privileged trait to call reveal() at the connection boundary. Removing Privileged causes the compiler to reject the reveal() call. See ek9 -h E11090 for details.

Incorrect:

DatabaseDriver with trait of DatabaseConsumer

Correct:

DatabaseDriver with trait of Privileged, DatabaseConsumer

E50060 — API keys for external services must not be hardcoded. Load them from environment variables using sensitiveGet(). See ek9 -h E50060 for details.

Incorrect:

apiKey <- "sk_live_abcdefghijklmnopqrstuvwxyz"

Correct:

apiKey <- env.sensitiveGet("PAYMENT_API_KEY")

E11080 — Cloud provider credentials must not be hardcoded. Load them from environment variables using sensitiveGet(). See ek9 -h E11080 for details.

Incorrect:

secret <- "AKIAIOSFODNN7EXAMPLE1"

Correct:

secret <- env.sensitiveGet(dbPasswordKey)
Other ways to ask this
  • How do I securely configure a database connection in EK9?
  • How do I pass API keys to an external service in EK9?
  • How do I prepare credentials for a database driver in EK9?

Coming from another language?

Java: plain Strings for credentials, any code can log/leak. EK9: Sensitive values throughout, only Privileged components can reveal().

Keywords: service, database, port, connection, password, username, reveal, credential, component, sensitive, privileged, connect, token, url, prepare, driver, key, api, host, configure, jdbc