Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

c# web api using auth1.0a with OAuthAuthorizationServerProvider

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.

like image 692
r3plica Avatar asked Aug 17 '17 14:08

r3plica


1 Answers

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

  1. Authentication as a Service
  2. Single Sign-on / Sign-out
  3. Access Control for APIs
  4. Federation Gateway

and for the most important part, it's free and open source.

like image 129
Pouya Samie Avatar answered Oct 19 '22 23:10

Pouya Samie