Username, email & password
The password
method is the most commonly used form of authentication, it requires an identifier
(username, email, phone
number, ...) and a password
during registration and login.
Ory Identities hashes the password after registration, password reset, and password change using:
- BCrypt (default).
- Argon2
Configuration
Enabling this method is as easy as setting
selfservice:
methods:
password:
enabled: true
in your Ory Identities configuration.
You can configure the BCrypt hasher using the following options:
hashers:
algorithm: bcrypt
BCrypt has a maximum length of 72 bytes for passwords. If a longer password is attempted to be used, an error will be returned to the user.
Bcrypt algorithm can be configured only by the following cost
option (default value is 12):
hashers:
bcrypt:
cost: 12
By default, Ory Identities uses BCrypt algorithm for password hashing. Use the following option to use the Argon2id algorithm:
hashers:
argon2:
parallelism: 1
memory: 128MB
iterations: 3
salt_length: 16
key_length: 32
To determine the ideal parameters, head over to the setup guide.
When a user signs up using this method, the Default Identity Schema (set using identity.default_schema_id
) is used:
identity:
default_schema_id: example-name
schemas:
- id: example-name
url: file:///path/to/default-identity.schema.json
# also works with HTTP(S) and Base64
#
# url: https://mydomain.com/path/to/default-identity.schema.json
# url: base64://...
To learn more about Identity Schemas, read this document.
For a complete reference, defaults, and description please check the Configuration Reference.
For a better understanding of security implications imposed by Argon2 Configuration, head over to Argon2 Security.
Choosing between username, email, and phone Number
Before you start, you need to decide what data you want to collect from your users and why! It's hard to change this decision afterwards, so make sure you've taken everything into account!
When logging in, the user will use a login identifier and a password to sign up and in. The identifier can be
- a username - "john.doe" or "johndoe123" or "oryuser",
- an email address -
john.doe@gmail.com
, - a phone number -
+49-1234-4321-1234-4321
.
All of these approaches have up- and downsides.
Using the email address as the login identifier is easy to remember, doesn't require additional fields (because the email address is already being collected), and is usually unique. It's usually unique because sometimes companies use a "shared" email account (for example office@acme.org) to access services. In that case, multiple real identities are using the same email identifier to log in.
The email address however represents a unique identifier and personally identifiable information (PII). An attacker could for
example check if the email address john.doe@gmail.com
is registered at for example an adult website and use that information for
blackmail (see Account Enumeration Attacks).
The same considerations apply to using a phone number as the primary registration & login identifier.
Using a free text username reduces the privacy risk because it's much harder to make a connection between the username and a real world identity. It's still possible in cases where users choose a username such as "john.doe.from.newyork.1970", but finding the right username identifier is still difficult and there is plausible deniability because anyone could use that username.
A free text username however requires capturing additional fields (for example an email address for password resets / account
recovery) and is hard to remember. It's often very difficult to find unique usernames as people tend to use a combination of their
names and initials such as john.doe
which has a high chance of collision. Therefore, one ends up with usernames such as
john.doe1234432
.
It's important to understand that Ory Identities lowercases all password
identifiers and therefore E-Mail addresses. Characters
+
or .
which have special meaning for some E-Mail Providers such as Gmail aren't normalized:
userNAME
is equal tousername
foo@BaR.com
is equal tofoo@bar.com
foo+baz@bar.com
is NOT equal tofoo@bar.com
foo.baz@bar.com
is NOT equal tofoobaz@bar.com
You need to decide which route you want to take.
Picking the right Identity Schema
When processing an identity and its traits, the method will use JSON Schema to extract one or more identifiers.
Use Case: Email and Password
To use the email address as the login identifier, define the following Identity JSON Schema:
{
"$id": "https://example.com/registration.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Person",
"type": "object",
"properties": {
"traits": {
"type": "object",
"properties": {
"email": {
"type": "string",
"format": "email",
"ory.sh/kratos": {
"credentials": {
"password": {
"identifier": true
}
}
}
}
}
}
}
}
Use case: multiple emails and password
You can allow users to sign up with multiple E-Mail Addresses and use any of those for log in:
{
"$id": "https://example.com/registration.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Person",
"type": "object",
"properties": {
"traits": {
"type": "object",
"properties": {
"emails": {
"type": "array",
"items": {
"type": "string",
"format": "email",
"ory.sh/kratos": {
"credentials": {
"password": {
"identifier": true
}
}
}
}
}
}
}
}
}
Use case: username and password
To use a username as the login identifier, define the following Identity JSON Schema:
{
"$id": "https://example.com/registration.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Person",
"type": "object",
"properties": {
"traits": {
"type": "object",
"properties": {
"username": {
"type": "string",
"ory.sh/kratos": {
"credentials": {
"password": {
"identifier": true
}
}
}
}
}
}
}
}
Use Case: username and email and password
You may also mix usernames and passwords:
{
"$id": "https://example.com/registration.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Person",
"type": "object",
"properties": {
"traits": {
"type": "object",
"properties": {
"email": {
"type": "string",
"format": "email",
"ory.sh/kratos": {
"credentials": {
"password": {
"identifier": true
}
}
}
},
"username": {
"type": "string",
"ory.sh/kratos": {
"credentials": {
"password": {
"identifier": true
}
}
}
}
}
}
}
}
Use case: phone number and password
{
"$id": "https://example.com/registration.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Person",
"type": "object",
"properties": {
"traits": {
"type": "object",
"properties": {
"phone": {
"type": "string",
"format": "tel",
"ory.sh/kratos": {
"credentials": {
"password": {
"identifier": true
}
}
}
}
}
}
}
}
Hashed password format
BCrypt
Ory Identities can hash passwords by BCrypt and can compare stored BCrypt hash and migrate if configured hasher
(hashers.algorithm
) isn't BCrypt.
Format
BCrypt format is described here.
Argon2
Ory Identities can hash passwords by Argon2 and can compare stored Argon2 hash and migrate if configured hasher
(hashers.algorithm
) isn't Argon2.
Format
$argon2id$v=<version>$m=<memory>,t=<iterations>,p=<parallelism>$<hash>
Parameters
version
(number
): The current version.memory
(number
): Amount of memory to use.iterations
(number
): Number of iterations to perform.parallelism
(number
): Degree of parallelism.hash
(string
): The computed derived key by the Argon2 algorithm encoded in base64.
Example
$argon2id$v=19$m=32,t=2,p=4$cm94YnRVOW5jZzFzcVE4bQ$MNzk5BtR2vUhrp6qQEjRNw
PBKDF2
Ory Identities doesn't hash passwords by PBKDF2 but can compare stored PBKDF2 hash and migrate to configured hasher
(hashers.algorithm
).
Format
$pbkdf2-<algorithm>$i=<iteration>,l=<length>$<salt>$<hash>
Parameters
digest
(string
): The HMAC digest algorithm applied to derive a key of the input password.iterations
(number
): The number of iterations desired. The higher the number of iterations, the more secure the derived key will be, but will take a longer amount of time to complete.length
(number
): Length in octets of derived key.salt
(string
): A sequence of bits, known as a cryptographic salt encoded in base64.hash
(string
): The computed derived key by the PBKDF2 algorithm encoded in base64.
Example
$pbkdf2-sha256$i=100000,l=32$1jP+5Zxpxgtee/iPxGgOz0RfE9/KJuDElP1ley4VxXc$QJxzfvdbHYBpydCbHoFg3GJEqMFULwskiuqiJctoYpI
SCrypt
Ory Identities doesn't hash passwords by SCrypt but can compare stored SCrypt hashes and migrate to configured hasher
(hashers.algorithm
).
Format
$scrypt$ln=<cost>,r=<block>,p=<parrelization>$<salt>$<hash>
Parameters
cost
(number
): CPU/memory cost (has to be power of 2 and >1)block
(number
): block size parameter (must satisfy r * p < 2³⁰)parallelization
(number
): parallelization parameter (must satisfy r * p < 2³⁰)salt
(string
): A sequence of bits, known as a cryptographic salt encoded in base64.hash
(string
): The computed derived key by the SCrypt algorithm encoded in base64.
Example
$scrypt$ln=16384,r=8,p=1$ZtQva9xCHzlSELH/mA7Kj5KjH2tCrkbwYzdxknkL0QQ=$pnTcXKaWVT+FwFDdk3vO1K0J7ZgOxdSU1tCJNYmn8zI=
Firebase SCrypt
Ory Identities doesn't hash passwords by Firebase SCrypt but can compare stored Firebase SCrypt hashes and migrate to configured
hasher (hashers.algorithm
).
Format
$firescrypt$ln=<mem_cost>,r=<rounds>,p=<parallelization>$<salt>$<hash>$<salt_separator>$<signer_key>
Parameters
mem_cost
(number
): CPU/memory cost as given byrounds
(number
): rounds firebase hash config parameter (must satisfy r * p < 2³⁰)parallelization
(number
): parallelization parameter (must satisfy r * p < 2³⁰)salt
(string
): A sequence of bits, known as a cryptographic salt encoded in base64.hash
(string
): The computed derived key by the SCrypt algorithm encoded in base64.salt_separator
(string
): Firebase hash config parameter encoded in base64.signer_key
(string
): Firebase hash config parameter encoded in base64.
Example
$firescrypt$ln=14,r=8,p=1$sPtDhWcd1MfdAw==$xbSou7FOl6mChCyzpCPIQ7tku7nsQMTFtyOZSXXd7tjBa4NtimOx7v42Gv2SfzPQu1oxM2/k4SsbOu73wlKe1A==$Bw==$YE0dO4bwD4JnJafh6lZZfkp1MtKzuKAXQcDCJNJNyeCHairWHKENOkbh3dzwaCdizzOspwr/FITUVlnOAwPKyw==
Example
Assuming your Identity Schema is as follows:
{
"$id": "https://example.com/example.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Person",
"type": "object",
"properties": {
"traits": {
"type": "object",
"properties": {
"first_name": {
"type": "string"
},
"email": {
"type": "string",
"format": "email",
"ory.sh/kratos": {
"credentials": {
"password": {
"identifier": true
}
}
}
},
"username": {
"type": "string",
"ory.sh/kratos": {
"credentials": {
"password": {
"identifier": true
}
}
}
}
},
"additionalProperties": false
}
}
}
And an identity registers with the following JSON payload (more on registration in Selfservice Registration):
{
"traits": {
"first_name": "John Doe",
"email": "john.doe@example.org",
"username": "johndoe123"
},
"password": "my-secret-password"
}
The password
method would generate a credentials block as follows:
credentials:
password:
id: password
identifiers:
- john.doe@example.org
- johndoe123
config:
hashed_password: ... # this would be a hash of `my-secret-password` string
Because credential identifiers need to be unique, no other identity can be created that has johndoe123
or john.doe@example.org
as their email
or username
.