Browse Source

Rename OpenIddictApplicationStore.GetHashedSecretAsync()/SetHashedSecretAsync()

pull/451/head
Kévin Chalet 9 years ago
parent
commit
89be252838
  1. 234
      src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs
  2. 34
      src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs
  3. 56
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs
  4. 5
      src/OpenIddict.Models/OpenIddictApplication.cs

234
src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs

@ -61,6 +61,8 @@ namespace OpenIddict.Core
/// <summary>
/// Creates a new application.
/// Note: the default implementation automatically hashes the client
/// secret before storing it in the database, for security reasons.
/// </summary>
/// <param name="application">The application to create.</param>
/// <param name="secret">The client secret associated with the application, if applicable.</param>
@ -78,7 +80,7 @@ namespace OpenIddict.Core
throw new ArgumentNullException(nameof(application));
}
if (!string.IsNullOrEmpty(await Store.GetHashedSecretAsync(application, cancellationToken)))
if (!string.IsNullOrEmpty(await Store.GetClientSecretAsync(application, cancellationToken)))
{
throw new ArgumentException("The client secret hash cannot be directly set on the application entity.");
}
@ -101,12 +103,13 @@ namespace OpenIddict.Core
throw new InvalidOperationException("A client secret must be provided when creating a confidential application.");
}
await Store.SetHashedSecretAsync(application, null, cancellationToken);
await Store.SetClientSecretAsync(application, null, cancellationToken);
}
else
{
await Store.SetHashedSecretAsync(application, Crypto.HashPassword(secret), cancellationToken);
secret = await ObfuscateClientSecretAsync(secret, cancellationToken);
await Store.SetClientSecretAsync(application, secret, cancellationToken);
}
await ValidateAsync(application, cancellationToken);
@ -309,7 +312,7 @@ namespace OpenIddict.Core
throw new ArgumentNullException(nameof(application));
}
return !string.IsNullOrEmpty(await Store.GetHashedSecretAsync(application, cancellationToken));
return !string.IsNullOrEmpty(await Store.GetClientSecretAsync(application, cancellationToken));
}
/// <summary>
@ -358,15 +361,37 @@ namespace OpenIddict.Core
}
/// <summary>
/// Updates the client secret associated with an application.
/// Updates an existing application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="application">The application to update.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual async Task UpdateAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
await ValidateAsync(application, cancellationToken);
await Store.UpdateAsync(application, cancellationToken);
}
/// <summary>
/// Updates an existing application and replaces the existing secret.
/// Note: the default implementation automatically hashes the client
/// secret before storing it in the database, for security reasons.
/// </summary>
/// <param name="application">The application to update.</param>
/// <param name="secret">The client secret associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual async Task SetClientSecretAsync([NotNull] TApplication application, [CanBeNull] string secret, CancellationToken cancellationToken)
public virtual async Task UpdateAsync([NotNull] TApplication application,
[CanBeNull] string secret, CancellationToken cancellationToken)
{
if (application == null)
{
@ -375,64 +400,119 @@ namespace OpenIddict.Core
if (string.IsNullOrEmpty(secret))
{
await Store.SetHashedSecretAsync(application, null, cancellationToken);
await Store.SetClientSecretAsync(application, null, cancellationToken);
}
else
{
await Store.SetHashedSecretAsync(application, Crypto.HashPassword(secret), cancellationToken);
secret = await ObfuscateClientSecretAsync(secret, cancellationToken);
await Store.SetClientSecretAsync(application, secret, cancellationToken);
}
await UpdateAsync(application, cancellationToken);
}
/// <summary>
/// Updates an existing application.
/// Validates the specified post_logout_redirect_uri.
/// </summary>
/// <param name="application">The application to update.</param>
/// <param name="address">The address that should be compared to the post_logout_redirect_uri stored in the database.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation, whose result
/// returns a boolean indicating whether the post_logout_redirect_uri was valid.
/// </returns>
public virtual async Task UpdateAsync([NotNull] TApplication application, CancellationToken cancellationToken)
public virtual async Task<bool> ValidateLogoutRedirectUriAsync(string address, CancellationToken cancellationToken)
{
// Warning: SQL engines like Microsoft SQL Server are known to use case-insensitive lookups by default.
// To ensure a case-sensitive comparison is used, string.Equals(Ordinal) is manually called here.
foreach (var application in await Store.FindByLogoutRedirectUriAsync(address, cancellationToken))
{
// Note: the post_logout_redirect_uri must be compared using case-sensitive "Simple String Comparison".
if (string.Equals(address, await Store.GetLogoutRedirectUriAsync(application, cancellationToken), StringComparison.Ordinal))
{
return true;
}
}
Logger.LogWarning("Client validation failed because '{PostLogoutRedirectUri}' " +
"was not a valid post_logout_redirect_uri.", address);
return false;
}
/// <summary>
/// Validates the redirect_uri associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="address">The address that should be compared to the redirect_uri stored in the database.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns a boolean indicating whether the redirect_uri was valid.
/// </returns>
public virtual async Task<bool> ValidateRedirectUriAsync([NotNull] TApplication application, string address, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
await ValidateAsync(application, cancellationToken);
await Store.UpdateAsync(application, cancellationToken);
// Note: the redirect_uri must be compared using case-sensitive "Simple String Comparison".
// See http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest for more information.
if (string.Equals(address, await Store.GetRedirectUriAsync(application, cancellationToken), StringComparison.Ordinal))
{
return true;
}
Logger.LogWarning("Client validation failed because '{RedirectUri}' was not a valid redirect_uri " +
"for '{Client}'.", address, await GetDisplayNameAsync(application, cancellationToken));
return false;
}
/// <summary>
/// Updates an existing application.
/// Validates the client_secret associated with an application.
/// </summary>
/// <param name="application">The application to update.</param>
/// <param name="secret">The client secret associated with the application.</param>
/// <param name="application">The application.</param>
/// <param name="secret">The secret that should be compared to the client_secret stored in the database.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns a boolean indicating whether the client secret was valid.
/// </returns>
public virtual async Task UpdateAsync([NotNull] TApplication application,
[CanBeNull] string secret, CancellationToken cancellationToken)
public virtual async Task<bool> ValidateClientSecretAsync([NotNull] TApplication application, string secret, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (string.IsNullOrEmpty(secret))
if (!await IsConfidentialAsync(application, cancellationToken))
{
await Store.SetHashedSecretAsync(application, null, cancellationToken);
Logger.LogWarning("Client authentication cannot be enforced for non-confidential applications.");
return false;
}
else
var value = await Store.GetClientSecretAsync(application, cancellationToken);
if (string.IsNullOrEmpty(value))
{
await Store.SetHashedSecretAsync(application, Crypto.HashPassword(secret), cancellationToken);
Logger.LogError("Client authentication failed for {Client} because " +
"no client secret was associated with the application.");
return false;
}
await UpdateAsync(application, cancellationToken);
if (!await ValidateClientSecretAsync(secret, value, cancellationToken))
{
Logger.LogWarning("Client authentication failed for {Client}.",
await GetDisplayNameAsync(application, cancellationToken));
return false;
}
return true;
}
/// <summary>
@ -469,17 +549,17 @@ namespace OpenIddict.Core
"supported by the default application manager.", nameof(application));
}
var hash = await Store.GetHashedSecretAsync(application, cancellationToken);
var secret = await Store.GetClientSecretAsync(application, cancellationToken);
// Ensure a client secret was specified if the client is a confidential application.
if (string.IsNullOrEmpty(hash) &&
if (string.IsNullOrEmpty(secret) &&
string.Equals(type, OpenIddictConstants.ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase))
{
throw new ArgumentException("The client secret cannot be null or empty for a confidential application.", nameof(application));
}
// Ensure no client secret was specified if the client is a public application.
else if (!string.IsNullOrEmpty(hash) &&
else if (!string.IsNullOrEmpty(secret) &&
string.Equals(type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase))
{
throw new ArgumentException("A client secret cannot be associated with a public application.", nameof(application));
@ -522,106 +602,60 @@ namespace OpenIddict.Core
}
/// <summary>
/// Validates the specified post_logout_redirect_uri.
/// </summary>
/// <param name="address">The address that should be compared to the post_logout_redirect_uri stored in the database.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation, whose result
/// returns a boolean indicating whether the post_logout_redirect_uri was valid.
/// </returns>
public virtual async Task<bool> ValidateLogoutRedirectUriAsync(string address, CancellationToken cancellationToken)
{
// Warning: SQL engines like Microsoft SQL Server are known to use case-insensitive lookups by default.
// To ensure a case-sensitive comparison is used, string.Equals(Ordinal) is manually called here.
foreach (var application in await Store.FindByLogoutRedirectUriAsync(address, cancellationToken))
{
// Note: the post_logout_redirect_uri must be compared using case-sensitive "Simple String Comparison".
if (string.Equals(address, await Store.GetLogoutRedirectUriAsync(application, cancellationToken), StringComparison.Ordinal))
{
return true;
}
}
Logger.LogWarning("Client validation failed because '{PostLogoutRedirectUri}' " +
"was not a valid post_logout_redirect_uri.", address);
return false;
}
/// <summary>
/// Validates the redirect_uri associated with an application.
/// Obfuscates the specified client secret so it can be safely stored in a database.
/// By default, this method returns a complex hashed representation computed using PBKDF2.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="address">The address that should be compared to the redirect_uri stored in the database.</param>
/// <param name="secret">The client secret.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns a boolean indicating whether the redirect_uri was valid.
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual async Task<bool> ValidateRedirectUriAsync([NotNull] TApplication application, string address, CancellationToken cancellationToken)
protected virtual Task<string> ObfuscateClientSecretAsync([NotNull] string secret, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
// Note: the redirect_uri must be compared using case-sensitive "Simple String Comparison".
// See http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest for more information.
if (string.Equals(address, await Store.GetRedirectUriAsync(application, cancellationToken), StringComparison.Ordinal))
if (string.IsNullOrEmpty(secret))
{
return true;
throw new ArgumentException("The secret cannot be null or empty.", nameof(secret));
}
Logger.LogWarning("Client validation failed because '{RedirectUri}' was not a valid redirect_uri " +
"for '{Client}'.", address, await GetDisplayNameAsync(application, cancellationToken));
return false;
return Task.FromResult(Crypto.HashPassword(secret));
}
/// <summary>
/// Validates the client_secret associated with an application.
/// Validates the specified value to ensure it corresponds to the client secret.
/// Note: when overriding this method, using a time-constant comparer is strongly recommended.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="secret">The secret that should be compared to the client_secret stored in the database.</param>
/// <param name="secret">The client secret to compare to the value stored in the database.</param>
/// <param name="comparand">The value stored in the database, which is usually a hashed representation of the secret.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns a boolean indicating whether the client secret was valid.
/// whose result returns a boolean indicating whether the specified value was valid.
/// </returns>
public virtual async Task<bool> ValidateClientSecretAsync([NotNull] TApplication application, string secret, CancellationToken cancellationToken)
protected virtual Task<bool> ValidateClientSecretAsync(
[NotNull] string secret, [NotNull] string comparand, CancellationToken cancellationToken)
{
if (application == null)
if (string.IsNullOrEmpty(secret))
{
throw new ArgumentNullException(nameof(application));
throw new ArgumentException("The secret cannot be null or empty.", nameof(secret));
}
if (!await IsConfidentialAsync(application, cancellationToken))
if (string.IsNullOrEmpty(comparand))
{
Logger.LogWarning("Client authentication cannot be enforced for non-confidential applications.");
return false;
throw new ArgumentException("The comparand cannot be null or empty.", nameof(comparand));
}
var hash = await Store.GetHashedSecretAsync(application, cancellationToken);
if (string.IsNullOrEmpty(hash))
try
{
Logger.LogError("Client authentication failed for {Client} because " +
"no client secret was associated with the application.");
return false;
return Task.FromResult(Crypto.VerifyHashedPassword(comparand, secret));
}
if (!Crypto.VerifyHashedPassword(hash, secret))
catch (Exception exception)
{
Logger.LogWarning("Client authentication failed for {Client}.",
await GetDisplayNameAsync(application, cancellationToken));
Logger.LogWarning(exception, "An error occurred while trying to verify a client secret. " +
"This may indicate that the hashed entry is corrupted or malformed.");
return false;
return Task.FromResult(false);
}
return true;
}
}
}

34
src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs

@ -99,37 +99,39 @@ namespace OpenIddict.Core
Task<string> GetClientIdAsync([NotNull] TApplication application, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the client type associated with an application.
/// Retrieves the client secret associated with an application.
/// Note: depending on the manager used to create the application,
/// the client secret may be hashed for security reasons.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the client type of the application (by default, "public").
/// whose result returns the client secret associated with the application.
/// </returns>
Task<string> GetClientTypeAsync([NotNull] TApplication application, CancellationToken cancellationToken);
Task<string> GetClientSecretAsync([NotNull] TApplication application, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the display name associated with an application.
/// Retrieves the client type associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the display name associated with the application.
/// whose result returns the client type of the application (by default, "public").
/// </returns>
Task<string> GetDisplayNameAsync([NotNull] TApplication application, CancellationToken cancellationToken);
Task<string> GetClientTypeAsync([NotNull] TApplication application, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the hashed secret associated with an application.
/// Retrieves the display name associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the hashed secret associated with the application.
/// whose result returns the display name associated with the application.
/// </returns>
Task<string> GetHashedSecretAsync([NotNull] TApplication application, CancellationToken cancellationToken);
Task<string> GetDisplayNameAsync([NotNull] TApplication application, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the unique identifier associated with an application.
@ -176,26 +178,28 @@ namespace OpenIddict.Core
Task<IEnumerable<string>> GetTokensAsync([NotNull] TApplication application, CancellationToken cancellationToken);
/// <summary>
/// Sets the client type associated with an application.
/// Sets the client secret associated with an application.
/// Note: depending on the manager used to create the application,
/// the client secret may be hashed for security reasons.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="type">The client type associated with the application.</param>
/// <param name="secret">The client secret associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
Task SetClientTypeAsync([NotNull] TApplication application, [NotNull] string type, CancellationToken cancellationToken);
Task SetClientSecretAsync([NotNull] TApplication application, [CanBeNull] string secret, CancellationToken cancellationToken);
/// <summary>
/// Sets the hashed secret associated with an application.
/// Sets the client type associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="hash">The hashed client secret associated with the application.</param>
/// <param name="type">The client type associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
Task SetHashedSecretAsync([NotNull] TApplication application, [CanBeNull] string hash, CancellationToken cancellationToken);
Task SetClientTypeAsync([NotNull] TApplication application, [NotNull] string type, CancellationToken cancellationToken);
/// <summary>
/// Updates an existing application.

56
src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs

@ -208,60 +208,62 @@ namespace OpenIddict.EntityFrameworkCore
}
/// <summary>
/// Retrieves the client type associated with an application.
/// Retrieves the client secret associated with an application.
/// Note: depending on the manager used to create the application,
/// the client secret may be hashed for security reasons.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the client type of the application (by default, "public").
/// whose result returns the client secret associated with the application.
/// </returns>
public virtual Task<string> GetClientTypeAsync([NotNull] TApplication application, CancellationToken cancellationToken)
public virtual Task<string> GetClientSecretAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
return Task.FromResult(application.Type);
return Task.FromResult(application.ClientSecret);
}
/// <summary>
/// Retrieves the display name associated with an application.
/// Retrieves the client type associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the display name associated with the application.
/// whose result returns the client type of the application (by default, "public").
/// </returns>
public virtual Task<string> GetDisplayNameAsync([NotNull] TApplication application, CancellationToken cancellationToken)
public virtual Task<string> GetClientTypeAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
return Task.FromResult(application.DisplayName);
return Task.FromResult(application.Type);
}
/// <summary>
/// Retrieves the hashed secret associated with an application.
/// Retrieves the display name associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the hashed secret associated with the application.
/// whose result returns the display name associated with the application.
/// </returns>
public virtual Task<string> GetHashedSecretAsync([NotNull] TApplication application, CancellationToken cancellationToken)
public virtual Task<string> GetDisplayNameAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
return Task.FromResult(application.ClientSecret);
return Task.FromResult(application.DisplayName);
}
/// <summary>
@ -353,49 +355,51 @@ namespace OpenIddict.EntityFrameworkCore
}
/// <summary>
/// Sets the client type associated with an application.
/// Sets the client secret associated with an application.
/// Note: depending on the manager used to create the application,
/// the client secret may be hashed for security reasons.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="type">The client type associated with the application.</param>
/// <param name="secret">The client secret associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetClientTypeAsync([NotNull] TApplication application, [NotNull] string type, CancellationToken cancellationToken)
public virtual Task SetClientSecretAsync([NotNull] TApplication application,
[CanBeNull] string secret, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (string.IsNullOrEmpty(type))
{
throw new ArgumentException("The client type cannot be null or empty.", nameof(type));
}
application.Type = type;
application.ClientSecret = secret;
return Task.FromResult(0);
}
/// <summary>
/// Sets the hashed secret associated with an application.
/// Sets the client type associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="hash">The hashed client secret associated with the application.</param>
/// <param name="type">The client type associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetHashedSecretAsync([NotNull] TApplication application,
[CanBeNull] string hash, CancellationToken cancellationToken)
public virtual Task SetClientTypeAsync([NotNull] TApplication application, [NotNull] string type, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
application.ClientSecret = hash;
if (string.IsNullOrEmpty(type))
{
throw new ArgumentException("The client type cannot be null or empty.", nameof(type));
}
application.Type = type;
return Task.FromResult(0);
}

5
src/OpenIddict.Models/OpenIddictApplication.cs

@ -45,8 +45,9 @@ namespace OpenIddict.Models
public virtual string ClientId { get; set; }
/// <summary>
/// Gets or sets the hashed client secret
/// associated with the current application.
/// Gets or sets the client secret associated with the current application.
/// Note: depending on the application manager used to create this instance,
/// this property may be hashed or encrypted for security reasons.
/// </summary>
public virtual string ClientSecret { get; set; }

Loading…
Cancel
Save