I set up JWT token based authentication using the OAuthAuthorizationServerProvider a while ago. The provider looks like this:
public class OAuthProvider : OAuthAuthorizationServerProvider
{
// Private properties
private readonly IAdvancedEncryptionStandardProvider _helper;
private readonly IUserProvider _userProvider;
// Optional fields
private readonly Lazy<IClientService> _clientService;
/// <summary>
/// Default constructor
/// </summary>
/// <param name="helper"></param>
public OAuthProvider(IAdvancedEncryptionStandardProvider helper, IUserProvider userProvider, Lazy<IClientService> clientService)
{
_helper = helper;
_userProvider = userProvider;
_clientService = clientService;
}
/// <summary>
/// Always validate the client because we are using angular
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
// Set up our variables
var clientId = string.Empty;
var clientSecret = string.Empty;
Client client = null;
// Try to get our credentials if basic authentication has been used
if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
context.TryGetFormCredentials(out clientId, out clientSecret);
// If we have no client id
if (context.ClientId == null)
{
//Remove the comments from the below line context.SetError, and invalidate context
//if you want to force sending clientId/secrects once obtain access tokens.
context.Validated();
//context.SetError("invalid_clientId", "ClientId should be sent.");
return;
}
// Get our client
client = await _clientService.Value.GetAsync(context.ClientId);
// If we have no client, throw an error
if (client == null)
{
context.SetError("invalid_clientId", $"Client '{ context.ClientId }' is not registered in the system.");
return;
}
// Get the application type
if (client.ApplicationType == ApplicationTypes.NativeConfidential)
{
// If we have a client secret
if (string.IsNullOrWhiteSpace(clientSecret))
{
context.SetError("invalid_clientId", "Client secret shoud be sent.");
return;
}
if (client.Secret != _helper.Encrypt(clientSecret))
{
context.SetError("invalid_clientId", "Client secret is invalid.");
return;
}
}
// If the client is inactive, throw an error
if (!client.Active)
{
context.SetError("invalid_clientId", "Client is inactive.");
return;
}
// Set our allowed origin and token expiration
context.OwinContext.Set<string>("as:clientAllowedOrigin", client.AllowedOrigin);
context.OwinContext.Set<string>("as:clientRefreshTokenLifeTime", client.RefreshTokenLifeTime.ToString());
// Validate our request
context.Validated();
return;
}
/// <summary>
/// Authorize the request
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
// Set our allowed origin
var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin");
if (string.IsNullOrEmpty(allowedOrigin))
allowedOrigin = "*";
// Add our CORS
context.OwinContext.Response.Headers.Remove("Access-Control-Allow-Origin");
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });
// Find user by username first
var user = await _userProvider.FindByNameAsync(context.UserName);
// If our user actually exists
if (user != null)
{
// Validate the users credentials
var validCredentials = await _userProvider.FindAsync(context.UserName, context.Password);
var lockoutEnabled = await _userProvider.GetLockoutEnabledAsync(user.Id);
// If lockout is enabled
if (lockoutEnabled)
{
// If the user entered invalid credentials
if (validCredentials == null)
{
// Record the failure which also may cause the user to be locked out
await _userProvider.AccessFailedAsync(user);
// Find out how many attempts are left
var accessFailedCount = await _userProvider.GetAccessFailedCountAsync(user.Id);
var attemptsLeft = Convert.ToInt32(ConfigurationManager.AppSettings["MaxFailedAccessAttemptsBeforeLockout"].ToString()) - accessFailedCount;
// Inform the user of the error
context.SetError("invalid_grant", string.Format(Resources.PasswordInvalid, attemptsLeft));
return;
}
// Check to see if the user is already locked out
var lockedOut = await _userProvider.IsLockedOutAsync(user.Id);
// If the user is lockted out
if (lockedOut)
{
// Inform the user
context.SetError("invalid_grant", string.Format(Resources.UserLocked, ConfigurationManager.AppSettings["DefaultAccountLockoutTimeSpan"].ToString()));
return;
}
// If we get this far, reset the access attempts
await _userProvider.ResetAccessFailedCountAsync(validCredentials);
}
// If the user entered the correct details
if (validCredentials != null)
{
// If the user has not confirmed their account
if (!validCredentials.EmailConfirmed)
{
// Inform the user
context.SetError("invalid_grant", Resources.UserHasNotConfirmed);
return;
}
// Generate our identity
var oAuthIdentity = await _userProvider.CreateIdentityAsync(validCredentials, "JWT");
oAuthIdentity.AddClaims(ExtendedClaimsProvider.GetClaims(validCredentials));
// Create our properties
var properties = new AuthenticationProperties(new Dictionary<string, string>
{
{"as:client_id", string.IsNullOrEmpty(context.ClientId) ? string.Empty : context.ClientId},
{"userName", context.UserName}
});
// Create our ticket and authenticate the user
var ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
return;
}
}
// Failsafe
context.SetError("invalid_grant", Resources.UserOrPasswordNotFound);
return;
}
/// <summary>
/// Adds additional properties to the response
/// </summary>
/// <param name="context">The current context</param>
/// <returns></returns>
public override Task TokenEndpoint(OAuthTokenEndpointContext context)
{
foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
context.AdditionalResponseParameters.Add(property.Key, property.Value);
return Task.FromResult<object>(null);
}
/// <summary>
/// Grants a refresh token for the current context
/// </summary>
/// <param name="context">The current context</param>
/// <returns></returns>
public override Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
{
// Get our client ids
var originalClient = context.Ticket.Properties.Dictionary["as:client_id"];
var currentClient = context.ClientId;
// If we are not the same client
if (originalClient != currentClient)
{
// Set the error and exit the function
context.SetError("invalid_clientId", "Refresh token is issued to a different clientId.");
return Task.FromResult<object>(null);
}
// Change auth ticket for refresh token requests
var newIdentity = new ClaimsIdentity(context.Ticket.Identity);
newIdentity.AddClaim(new Claim("newClaim", "newValue"));
var newTicket = new AuthenticationTicket(newIdentity, context.Ticket.Properties);
context.Validated(newTicket);
return Task.FromResult<object>(null);
}
}
This has worked well and for the most part is fine. My manager how now asked me to put authentication in for other applications using a key and secret. I would like to use the OAuthAuthorizationServerProvider to do this, but I cannot find any documentation anywhere of how to go about setting this up.
I have read and found a method which can be override: GrantCustomExtension and thought that maybe I could use this to set up the authentication but like I have mentioned, I have no idea how to set it up.
Has anyone had experience with this? If they have, could they help me by providing a code example or giving me a link to a resource that I can read? Any help would be greatly appreciated.
I recommend to step away from Asp.net Identity and use IdentityServer.
IdentityServer4 is an OpenID Connect and OAuth 2.0 framework for ASP.NET Core. and IdentityServer3 is for Asp.Net Classic ( although you can use IdentityServer4 with Asp.net classic )
it's really easy to config and very ongoing project.
it has several features like
and for the most important part, it's free and open source.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With