Class: WSDL::Security::UsernameToken

Inherits:
Object
  • Object
show all
Defined in:
lib/wsdl/security/username_token.rb

Overview

Note:

The digest algorithm (SHA-1) is mandated by the WS-Security UsernameToken Profile 1.1 specification and cannot be changed without breaking interoperability with compliant servers.

Represents a WS-Security UsernameToken element.

The UsernameToken provides username/password authentication for SOAP messages. It supports both plain text and digest password modes.

== Security Comparison

Choose the appropriate mode based on your security requirements:

Plain Text Mode

  • Password is sent as-is in the SOAP message
  • Must only be used over HTTPS to protect the password in transit
  • Simpler but provides no replay protection
  • Suitable for: Development, trusted networks with HTTPS

Digest Mode

  • Password is never transmitted
  • Computed as: +Base64(SHA-1(nonce + created + password))+
  • Provides replay attack protection via nonce and timestamp
  • Even if intercepted, attacker cannot recover the original password
  • Suitable for: Production environments, sensitive operations

X.509 Certificate Signatures

  • For high-security requirements, consider using Signature instead
  • Supports SHA-256/SHA-512 (configurable)
  • Provides non-repudiation and stronger cryptographic guarantees
  • Suitable for: Compliance requirements, financial transactions

== SHA-1 Protocol Limitation

The WS-Security UsernameToken Profile 1.1 specification mandates SHA-1 for password digests. This is a protocol constraint, not a design choice. Servers expecting WS-Security compliance will reject non-SHA-1 digests.

While SHA-1 has known weaknesses (collision attacks), these do not directly impact password digest security:

  • Collision attacks find two inputs with the same hash — not useful for password cracking
  • Preimage resistance (recovering input from hash) remains computationally infeasible
  • The nonce changes every request, preventing precomputation attacks
  • The real risk is weak passwords, not SHA-1 itself

For scenarios requiring stronger cryptographic algorithms, use X.509 certificate signatures which support SHA-256/SHA-512.

== Recommendations

  1. Always use HTTPS — regardless of password mode
  2. Prefer digest mode over plain text for production
  3. Use strong passwords — this is the primary security factor
  4. Consider X.509 signatures for high-security requirements

Examples:

Plain text password (use only over HTTPS)

token = UsernameToken.new('user', 'secret')

Digest password (recommended for production)

token = UsernameToken.new('user', 'secret', digest: true)

See Also:

Constant Summary collapse

PasswordType =

Local aliases for token profile constants

Constants::TokenProfiles::UsernameToken
Encoding =

Alias for encoding URI constants used in UsernameToken XML.

Returns:

  • (Module)
Constants::Encoding

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(username, password, digest: false, created_at: nil, id: nil) ⇒ UsernameToken

Creates a new UsernameToken instance.

Parameters:

  • username (String)

    the username

  • password (String)

    the password

  • digest (Boolean) (defaults to: false)

    whether to use digest authentication (default: false)

  • created_at (Time, nil) (defaults to: nil)

    the creation time (defaults to current UTC time)

  • id (String, nil) (defaults to: nil)

    the wsu:Id attribute (auto-generated if nil)



110
111
112
113
114
115
116
117
# File 'lib/wsdl/security/username_token.rb', line 110

def initialize(username, password, digest: false, created_at: nil, id: nil)
  @username = username
  @password = password
  @digest = digest
  @created_at = (created_at || Time.now).utc
  @id = id || IdGenerator.for('UsernameToken')
  @nonce = generate_nonce if digest?
end

Instance Attribute Details

#created_atTime (readonly)

Returns the creation timestamp.

Returns:

  • (Time)


96
97
98
# File 'lib/wsdl/security/username_token.rb', line 96

def created_at
  @created_at
end

#idString (readonly)

Returns the unique ID for this token element.

Returns:

  • (String)


100
101
102
# File 'lib/wsdl/security/username_token.rb', line 100

def id
  @id
end

#nonceString? (readonly)

Returns the nonce value (only present in digest mode).

Returns:

  • (String, nil)

    the raw nonce bytes, or nil if not using digest



131
132
133
# File 'lib/wsdl/security/username_token.rb', line 131

def nonce
  @nonce
end

#passwordString (readonly)

Returns the password (plain text, before digest if applicable).

Returns:

  • (String)


92
93
94
# File 'lib/wsdl/security/username_token.rb', line 92

def password
  @password
end

#usernameString (readonly)

Returns the username.

Returns:

  • (String)


88
89
90
# File 'lib/wsdl/security/username_token.rb', line 88

def username
  @username
end

Instance Method Details

#created_at_xmlString

Returns the created timestamp as an XML Schema dateTime string.

Returns:

  • (String)

    ISO 8601 formatted timestamp



147
148
149
# File 'lib/wsdl/security/username_token.rb', line 147

def created_at_xml
  @created_at.xmlschema
end

#digest?Boolean

Returns whether digest authentication is enabled.

Returns:

  • (Boolean)

    true if using digest mode



123
124
125
# File 'lib/wsdl/security/username_token.rb', line 123

def digest?
  @digest
end

#encoded_nonceString?

Returns the nonce encoded as Base64 (for XML output).

Returns:

  • (String, nil)

    the Base64-encoded nonce, or nil if not using digest



137
138
139
140
141
# File 'lib/wsdl/security/username_token.rb', line 137

def encoded_nonce
  return nil unless @nonce

  Base64.strict_encode64(@nonce)
end

#inspectString

Returns a safe string representation that hides sensitive values.

This method ensures that passwords and nonces are never accidentally exposed in logs, error messages, debugger output, or stack traces.

Examples:

token = UsernameToken.new('admin', 'super_secret', digest: true)
token.inspect
# => '#<WSDL::Security::UsernameToken username="admin" password=[REDACTED] digest=true nonce=[REDACTED]>'

Returns:

  • (String)

    a redacted representation safe for logging



251
252
253
254
255
256
257
258
259
260
# File 'lib/wsdl/security/username_token.rb', line 251

def inspect
  parts = [
    "username=#{@username.inspect}",
    'password=[REDACTED]',
    "digest=#{@digest}"
  ]
  parts << 'nonce=[REDACTED]' if @nonce

  "#<#{self.class.name} #{parts.join(' ')}>"
end

#password_typeString

Returns the password type URI for the XML Type attribute.

Returns:

  • (String)

    the password type URI



170
171
172
173
174
175
176
# File 'lib/wsdl/security/username_token.rb', line 170

def password_type
  if digest?
    PasswordType::PASSWORD_DIGEST
  else
    PasswordType::PASSWORD_TEXT
  end
end

#password_valueString

Returns the password value to include in the XML.

For plain text mode, this is the original password. For digest mode, this is Base64(SHA-1(nonce + created + password)).

Returns:

  • (String)

    the password or digest value



158
159
160
161
162
163
164
# File 'lib/wsdl/security/username_token.rb', line 158

def password_value
  if digest?
    compute_digest
  else
    @password
  end
end

#to_hashHash

Returns a Hash representation suitable for Gyoku XML generation.

Returns:

  • (Hash)

    the token structure as a hash



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/wsdl/security/username_token.rb', line 213

def to_hash
  token = {
    'wsse:Username' => @username,
    'wsse:Password' => password_value,
    :attributes! => {
      'wsse:Password' => { 'Type' => password_type }
    }
  }

  if digest?
    token['wsse:Nonce'] = encoded_nonce
    token['wsu:Created'] = created_at_xml
    token[:attributes!]['wsse:Nonce'] = { 'EncodingType' => Encoding::BASE64 }
    token[:order!] = ['wsse:Username', 'wsse:Password', 'wsse:Nonce', 'wsu:Created']
  else
    token[:order!] = ['wsse:Username', 'wsse:Password']
  end

  {
    'wsse:UsernameToken' => token,
    :attributes! => {
      'wsse:UsernameToken' => { 'wsu:Id' => @id }
    }
  }
end

#to_xml(xml) ⇒ void

This method returns an undefined value.

Builds the XML representation of the UsernameToken element.

Examples:

Output XML structure (plain text)

<wsse:UsernameToken wsu:Id="UsernameToken-abc123">
  <wsse:Username>user</wsse:Username>
  <wsse:Password Type="...#PasswordText">secret</wsse:Password>
</wsse:UsernameToken>

Output XML structure (digest)

<wsse:UsernameToken wsu:Id="UsernameToken-abc123">
  <wsse:Username>user</wsse:Username>
  <wsse:Password Type="...#PasswordDigest">digest_value</wsse:Password>
  <wsse:Nonce EncodingType="...#Base64Binary">nonce_value</wsse:Nonce>
  <wsu:Created>2026-02-01T12:00:00Z</wsu:Created>
</wsse:UsernameToken>

Parameters:

  • xml (Nokogiri::XML::Builder)

    the XML builder



197
198
199
200
201
202
203
204
205
206
207
# File 'lib/wsdl/security/username_token.rb', line 197

def to_xml(xml)
  xml['wsse'].UsernameToken('wsu:Id' => @id) do
    xml['wsse'].Username(@username)
    xml['wsse'].Password(password_value, 'Type' => password_type)

    if digest?
      xml['wsse'].Nonce(encoded_nonce, 'EncodingType' => Encoding::BASE64)
      xml['wsu'].Created(created_at_xml)
    end
  end
end