Saturday, 26 May 2018

OAuth Integration with MVC WebApi


What is OAuth

oAuth stands for Open Authorization. It helps to access the resource in secured way , when the resource owner want it to share to any third party provider.
Like you logged in to the twitter and now you want to send a friend request to all your google address book user. So you can login securely to the google and than google will authenticate and share the entire address book to twitter and that you can use to send the friend request.

How it Work


User has logged into as is user credential  into seeker Resource (SR) now he want to get the details from third party resource(RO)  where he already have account. He has to provide the credentials than third party resource owner (RO) will allocate the security token. Then Seeker Resource (SR) will send the Security token to the resource Owner(RO), and it will give the details of resource what Seeker Resource want.



Implementation

1.       Create a sample WebApi  using the visual studio as a default api.
2.       Now browse your WebApi. Just to test that your API is working fine or not.

Install below Nuget packages in your applications.

a.       Microsoft.owin.security.oauth:
Implemantaion of Oauth Services. This is the iddleware that will handle token generation.
b.      Microsoft.Owin.Cors:
Allow you to manage Cors security for client side requests and validation of proper domains. This package contains the components to enable Cross-Origin Resource Sharing (CORS) in OWIN middleware.
c.       Microsoft.AspNet.WebApi.Core
This package contains the core runtime assemblies for ASP.NET Web API. This package is used by hosts of the ASP.NET Web API runtime. To host a Web API in IIS use the Microsoft.AspNet.WebApi.WebHost package. To host a Web API in your own process use the Microsoft.AspNet.WebApi.SelfHost package. [ Its core package of Web Api]
It Might be already installed.
d.      Microsoft.AspNet.WebApi.Owin
This package allows you to host ASP.NET Web API within an OWIN server and provides access to additional OWIN features.
e.      Microsoft.Owin.Host.SystemWeb
OWIN server that enables OWIN-based applications to run on IIS using the ASP.NET request pipeline.
f.        Microsoft.AspNet.Identity.Owin
g.       Microsoft.AspNet.Identity.EntityFramework
It helps to maintain the identity into entity frameworks and code first approach.

Configuration in web API.

Open WebApiConfig.cs and add the below code. In WebApiConfig

    public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services
            // Configure Web API to use only bearer token authentication.
            config.SuppressDefaultHostAuthentication();
            config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));

            // Web API routes
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

            /// It is using only if you are sending the Form type data
            config.Formatters.XmlFormatter.SupportedMediaTypes.Add(new System.Net.Http.Headers.MediaTypeHeaderValue("multipart/form-data"));
        }

Ø  first line of code removes any default authentication which might be defined at the host level
Ø  Second line of code adds an authentication filter for OAuth that it will be the only one applied.

Startup.cs


Add a startup.cs class to your application.
a.       Rt click on App_Start à Add à Class
b.      Now search for OWIN at right search box

(Follow Below Link For more detail)

c.       Added below code in  startup.cs page it is partial class.

  [assembly: OwinStartup(typeof(WebApiOAuth_1.App_Start.Startup))]

namespace WebApiOAuth_1.App_Start
{
    public partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {
           var config = new HttpConfiguration();
            ConfigureAuth(app);
            app.UseCors(CorsOptions.AllowAll);
            app.UseWebApi(config);
        }

    }
}

Configuration of authentication ConfigureAuth it is defined in Startup.auth.cs. Just a configuration of OAuth. Configuring the CORS for application. By calling app.UseWebApi you configure OWIN/Katana to send web requests through ASP.NET Web Api that in OWIN terminology is considered a middleware.

Startup.Auth.cs

Add file Startup.Auth.cs for maintaining the authorization.


Content for Startup.Auth.cs
public partial class Startup
    {
        public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }

        public static string PublicClientId { get; private set; }

        // For more information on configuring authentication, please visit https://go.microsoft.com/fwlink/?LinkId=301864
        public void ConfigureAuth(IAppBuilder app)
        {
            // Configure the db context and user manager to use a single instance per request
            app.CreatePerOwinContext(ApplicationDbContext.Create);
            app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);

            // Enable the application to use a cookie to store information for the signed in user
            // and to use a cookie to temporarily store information about a user logging in with a third party login provider
            app.UseCookieAuthentication(new CookieAuthenticationOptions());
            app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

            // Configure the application for OAuth based flow
            PublicClientId = "self";
            OAuthOptions = new OAuthAuthorizationServerOptions
            {
                TokenEndpointPath = new PathString("/Token"),
                Provider = new ApplicationOAuthProvider(PublicClientId),
                AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
                AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
                // In production mode set AllowInsecureHttp = false
                AllowInsecureHttp = true
            };

            // Enable the application to use bearer tokens to authenticate users
            app.UseOAuthBearerTokens(OAuthOptions);

            // Uncomment the following lines to enable logging in with third party login providers
            //app.UseMicrosoftAccountAuthentication(
            //    clientId: "",
            //    clientSecret: "");

            //app.UseTwitterAuthentication(
            //    consumerKey: "",
            //    consumerSecret: "");

            //app.UseFacebookAuthentication(
            //    appId: "",
            //    appSecret: "");

            //app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
            //{
            //    ClientId = "",
            //    ClientSecret = ""
            //});
        }
    }

IdentityConfig.cs

It uses to manage the user configuration. Configure the application user manager used in this application. UserManager is defined in ASP.NET Identity and is used by the application.
namespace WebApiOAuth_1
{
    // Configure the application user manager used in this application. UserManager is defined in ASP.NET Identity and is used by the application.

    public class ApplicationUserManager : UserManager<ApplicationUser>
    {
        public ApplicationUserManager(IUserStore<ApplicationUser> store)
            : base(store)
        {
        }

        public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
        {
            var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
            // Configure validation logic for usernames
            manager.UserValidator = new UserValidator<ApplicationUser>(manager)
            {
                AllowOnlyAlphanumericUserNames = false,
                RequireUniqueEmail = true
            };
            // Configure validation logic for passwords
            manager.PasswordValidator = new PasswordValidator
            {
                RequiredLength = 6,
                RequireNonLetterOrDigit = true,
                RequireDigit = true,
                RequireLowercase = true,
                RequireUppercase = true,
            };
            var dataProtectionProvider = options.DataProtectionProvider;
            if (dataProtectionProvider != null)
            {
                manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
            }
            return manager;
        }

     
    }
}

IdentityModels.cs

You can add profile data for the user by adding more properties to your ApplicationUser class.
namespace WebApiOAuth_1.Models
{

    // You can add profile data for the user by adding more properties to your ApplicationUser class, please visit https://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
    public class ApplicationUser : IdentityUser
    {
        public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager, string authenticationType)
          {
              // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
              var userIdentity = await manager.CreateIdentityAsync(this, authenticationType);



              // Add custom user claims here

              return userIdentity;
          }
       

        public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
          {
              var userIdentity = await GenerateUserIdentityAsync(manager, DefaultAuthenticationTypes.ApplicationCookie);
              return userIdentity;
          }
         
      
    }

    public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
        private const string LocalLoginProvider = "Local";
      

        public ApplicationDbContext()
            : base("DefaultConnection", throwIfV1Schema: false)
        {
        }


        public async void CreateUsers()
        {
            ApplicationDbContext applicationDbContext = new ApplicationDbContext();
            ApplicationUser applicationUser = new ApplicationUser();

            //IdentityUserClaim identityUserClaim = new IdentityUserClaim();
            //identityUserClaim.UserId = "satya";

            applicationUser.PasswordHash = "satya12345";
            applicationUser.UserName = "satya";

            //applicationUser.Claims.Add(identityUserClaim);

            //UserManager o = new UserManager<ApplicationUser, string>();

            //UserManager<ApplicationUser> o = new UserManager(;
            //await applicationUser.GenerateUserIdentityAsync(applicationUser).Wait();

            //  applicationUser.GenerateUserIdentityAsync()
            applicationDbContext.Users.Add(applicationUser);
        }



         public static ApplicationDbContext Create()
        {

          

            return new ApplicationDbContext();
        }
    }
}

ChallengeResult.cs

It is default implementation for OAuth Provider. The file model structure is



namespace WebApiOAuth_1.oAuthConfiguration
{
    public class ChallengeResult : IHttpActionResult
    {
        public ChallengeResult(string loginProvider, ApiController controller)
        {
            LoginProvider = loginProvider;
            Request = controller.Request;
        }

        public string LoginProvider { get; set; }
        public HttpRequestMessage Request { get; set; }


       public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
        {
            Request.GetOwinContext().Authentication.Challenge(LoginProvider);

            var response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
            response.RequestMessage = Request;
            return Task.FromResult(response);
        }
    }
}

ApplicationOAuthProvider

This is the actual implementation of your Oauth provider, here only it will validate your username and password which is added into Identity.
namespace WebApiOAuth_1.oAuthConfiguration
{
    public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
    {
        private readonly string _publicClientId;


        public ApplicationOAuthProvider(string publicClientId)
        {
            //TODO: Pull from configuration
            if (publicClientId == null)
            {
                throw new ArgumentNullException(nameof(publicClientId));
            }

            _publicClientId = publicClientId;
        }

        public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
        {

            var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();

            var user = await userManager.FindAsync(context.UserName, context.Password);  //  .FindAsync(context.UserName, context.Password);

            if (user == null)
            {
                context.SetError("invalid_grant", "The user name or password is incorrect.");
                return;
            }

            ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager,
                OAuthDefaults.AuthenticationType);
            ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager,
                CookieAuthenticationDefaults.AuthenticationType);

            AuthenticationProperties properties = CreateProperties(user.UserName);
            AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
            context.Validated(ticket);
            context.Request.Context.Authentication.SignIn(cookiesIdentity);
        }

        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);
        }

        public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {
            // Resource owner password credentials does not provide a client ID.
            if (context.ClientId == null)
            {
                context.Validated();
            }

            return Task.FromResult<object>(null);
        }

        public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
        {
            if (context.ClientId == _publicClientId)
            {
                Uri expectedRootUri = new Uri(context.Request.Uri, "/");

                if (expectedRootUri.AbsoluteUri == context.RedirectUri)
                {
                    context.Validated();
                }
            }

            return Task.FromResult<object>(null);
        }

        public static AuthenticationProperties CreateProperties(string userName)
        {
            IDictionary<string, string> data = new Dictionary<string, string>
        {
            { "userName", userName }
        };
            return new AuthenticationProperties(data);
        }
    }
}

API Controller Code

Code to get the values. Just need to add the [Authorize] on top of the action method.

Get Method.

  [Authorize]
        [HttpGet]
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }

Register New Identity.

It use to register new identity to application and only for that user, you can grant the access token.
private ApplicationUserManager _userManager;
      
        public void AddUsers(string userName, string password)
        {
            //var user = new ApplicationUser() { UserName = userName, Email = "satya@gmail.com" };
            //IdentityResult result = await UserManager.CreateAsync(user, password);

            var userStore = new UserStore<IdentityUser>();
            var manager = new UserManager<IdentityUser>(userStore);
            var user = new IdentityUser() { UserName = userName };

            IdentityResult result = manager.Create(user, password);

            if (result.Succeeded)
            {
                var authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
                var userIdentity = manager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie);
            }
        }


        public ApplicationUserManager UserManager
        {
            get
            {
                return _userManager ?? Request.GetOwinContext().GetUserManager<ApplicationUserManager>();
            }
            private set
            {
                _userManager = value;
            }
        }

        [HttpPost]
        public HttpResponseMessage Post([FromBody]string value)
        {
            try
            {
                var UserName = value.Split('|')[0];
                var Password = value.Split('|')[1];

                AddUsers(UserName, Password);
                return new HttpResponseMessage(HttpStatusCode.Accepted);
            }
            catch
            {
                return new HttpResponseMessage(HttpStatusCode.NotImplemented);
            }
        }

WebApi Testing  with OAuth Security (Postman)


Secure URL without Token

Now if you try to access the Application without token. Same URL As we seen above
you can get message as "Authorization has been denied"


Fetch token for Unregistered User


Trying to request for token if not registered. 
  • Use Post
  • Access Toke URL as http://localhost:[12345]/token/ 
  • select content type as application/x-www-form-urlencoded
  • Pass as header: grant_type, username, password, 


You will get message as "Unsupported grant_type" as below




Access the services  à register new User


You need to add the Identity for application by which only you can validate the Request.
  • Use Post
  • Add identity URL http://localhost:[12345]/api/values/post
  • select content type as JSON(application/JSON)
  • Pass as Body: plan text  satya|satya12345
Adding Identity as satya|satya12345


Fetch Token for Registered User


You need to get the token  by using the token URL. 
By Passing the UserName and Password.
Use same above Setup for postman for fetching tokens.




Access URL with Authorization Key

Once you get the Access token than access the URL With the Authorization Token as like below:


pass the token you got in last postman fetch result as like below where key is Authorization  
and value as

Bearer DBu8L7mwTKEyhgAkO1m3o604RCok6gHiEUrbSZNk5BcWHkZJTc0KqgriyEjnHMXtH_w_tKAF_RvVGBK3GuwPeJwy7ZLHjmHn-ny4Aeopy2GUEqNOl_BG1wh02b6phsW2-yvXJgY8WQqhRkO28Mjk5bvfO26usbSG5ukEcs0bNTAkJBfd8r1jDC_k17krX5w622Kq5lneNXlnNIwwMO8wByEhFqbWWCGh1ne_Fc3oXVAT-zUMtbQiJ--E9dd5w66ERyn_UweOC2D_dpx2shnMNEJpMzdvyFnBHjImvmkdb8ugv_vapvLVt8uhWIAbPob-0pcQrJn4JRVnNTb3Fv6rqbfY_zWfnAl8miBdkgE0zJrO4qxJ7BTAReuZ7LfU9DYyBUDcZzt3BIwI1_UHUiftBfrSz5bQVBkgtwztBr4iblniN5U6TaiTMzdPa6Z3K2uoOCI4YNL9bhhJlDdaHY--aNOzdMcGAi2QWXPo9RJ6JCY

Full Post man screen request:




No comments:

Post a Comment