Client secret store codec
1. Encrypt or hash client secrets in storage
Connect2id server deployments with a backend
database which doesn’t encrypt its
data can implement server-side encryption of the client secrets. This is
enabled by a dedicated plugin interface
(SPI) for applying
transformations to the client_secret
before writing it to the database, and
then again upon its retrieval.
Encrypting, or alternatively, hashing the client secrets, is a recommended security measure to minimise the risk of an undetected credential leak from the database.
Recommended methods:
Note, while hashing is easier to implement because there is no symmetric key to
be managed, it cannot be used with a client using client_secret_jwt
authentication or an HMAC JOSE algorithm that requires the Connect2id server to
have the secret in clear text. This means clients registered for the following
must use encryption instead of hashing of the client_secret
:
- Clients registered for client_secret_jwt authentication.
- Clients registered or ID token, UserInfo, request object (JAR) or JARM with the JWS algorithms HS256, HS384 or HS512.
- Clients registered to receive or submit JWTs encrypted with a shared AES key derived from the secret, the JWE algorithm dir.
Connect2id server deployment can also choose to dispense with the client secrets entirely, which is a wise security strategy, by allowing only public key based authentication - private_key_jwt, or the tls_client_auth / self_signed_tls_client_auth methods in RFC 8705.
The codec plugin SPI can also be used to import client secrets of arbitrary format from another server.
2. Client secret codec SPI
To plug in your own custom codec implement the ClientSecretStoreCodec SPI defined in the Connect2id server toolkit:
Git repo | https://bitbucket.org/connect2id/server-sdk |
---|
Features of the client secret codec SPI:
- Supports arbitrary encoding of
client_secret
values before writing them to the database. - Supports arbitrary decoding of
client_secret
values after retrieving them from the database. - Supports separate encoding / decoding of
client_secret
values for clients imported from another OAuth 2.0 server. - Provides access to the registered information for the client so that encoding decisions (e.g. encrypt or hash) can be made.
- Provides access to the Connect2id server JWK set where the client secret encryption AES key(s) can be stored.
If the Connect2id server detects an SPI implementation it will log its loading
under OP0138
.
INFO main MAIN - [OP0138] Loaded client secret store codec: com.nimbusds.openid.connect.provider.spi.secrets.impl.BCryptSecretCodec
3. Example codec
Sample BCrypt hashing codec with the BCrypt Java library:
import at.favre.lib.crypto.bcrypt.BCrypt;
import com.nimbusds.openid.connect.provider.spi.secrets.*;
import com.nimbusds.oauth2.sdk.auth.Secret;
import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
public class BCryptCodec implements ClientSecretStoreCodec {
@Override
public String encode(final Secret secret, final SecretCodecContext ctx) {
return BCrypt
.withDefaults()
.hashToString(4, secret.getValue().toCharArray());
}
@Override
public DecodedSecret decode(final String storedValue, final SecretCodecContext ctx) {
return DecodedSecret.createForHashedSecret(
otherSecret -> {
BCrypt.Result result = BCrypt
.verifyer()
.verify(
otherSecret.getValue().toCharArray(),
storedValue.toCharArray());
return result.verified;
}
);
}
}
Example transformation with BCrypt:
Plain text 256-bit secret, BASE64URL encoded: 5k4NOArtKpDYeBoxDoVwXswsIApyibpMIBWRgLdSyNM
BCrypt: $2a$04$a9uQ9Ka0usxqTCp/1je2iuS.qnVsXKe0Gjhh5kPEhnbInkseODhgS
4. Effect on the client registration API
If the codec cannot reverse the client secret because it is hashed the
client information responses
will contain a client_secret
, but its value will be set to null
.
Example response indicating a hashed client secret:
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache
{
"client_id" : "s6BhdRkqt3",
"client_secret" : null,
"client_secret_expires_at" : 1577858400
}
5. How to return the encoded secret in the client registration API
To return the encoded (stored) client secret in client information responses the codec plugin must call the withEncodedValue method before returning the DecodedSecret.
@Override
public DecodedSecret decode(final String storedValue, final SecretCodecContext ctx) {
return DecodedSecret.createForHashedSecret(
otherSecret -> {
BCrypt.Result result = BCrypt
.verifyer()
.verify(
otherSecret.getValue().toCharArray(),
storedValue.toCharArray());
return result.verified;
}
).withEncodedValue(storedValue);
}
The encoded value will then appear in a custom client_secret_encoded
field.
Example response including the hashed client secret value:
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache
{
"client_id" : "s6BhdRkqt3",
"client_secret" : null,
"client_secret_encoded" : "$2a$04$a9uQ9Ka0usxqTCp/1je2iuS.qnVsXKe0Gjhh5kPEhnbInkseODhgS",
"client_secret_expires_at" : 1577858400
}
Note, the client_secret_encoded
will appear only in client
read responses.
6. How to import hashed client secrets
The custom preferred_client_secret
field in a client registration request can
be used to import hashed or
otherwise encoded secrets from another OAuth 2.0 / OpenID Connect server. The
value of the preferred_client_secret
will appear as input to the
encodedImported
method of the ClientSecretCodec.