Thursday, 15 March 2018

Implementing authentication using Azure Active Directory and Microsoft Office 365.

Step 1: Login into azure portal using https://portal.azure.com, after login follow the steps mentioned in following screens:





Step 2: Add the follwing keys in your web.config file:
  <appSettings>
    <!--Azure settings-->
    <add key="AzureADInstance" value="https://login.microsoftonline.com/" />
    <add key="DirectoryId" value="<YOUR AZURE DIRECTORY ID>"/>
    <add key="ClientId" value="<YOUR AZURE APPLICATION ID>"/>
    <add key="ClientSecret" value="<SECRET KEY FROM YOUR AZURE APPLICATION>"/>
    <add key="RedirectUri" value="https://localhost:100/User/AADLoginCallback"/>
    <add key="GrantType" value="authorization_code"/>
    <add key="ResponseType" value="code"/>
    <add key="ResponseMode" value="query"/>
    <add key="LogoutUri" value="https://localhost:100/user/Logout"/>
    <add key="Resource" value="https://graph.microsoft.com"/>
    <add key="GraphAPIUri" value="https://graph.microsoft.com/v1.0"/>
  </appSettings>

Step 3: Add new class named "AADAuthUtils.cs" under Utils folder and paste the following c# code:
 public class AADAuthUtils
    {
        private static string ClientId = ConfigurationManager.AppSettings["ClientId"],
            ResponseType = ConfigurationManager.AppSettings["ResponseType"],
            RedirectUri = ConfigurationManager.AppSettings["RedirectUri"],
            ResponseMode = ConfigurationManager.AppSettings["ResponseMode"],
            AzureADInstance = ConfigurationManager.AppSettings["AzureADInstance"],
            DirectoryId = ConfigurationManager.AppSettings["DirectoryId"],
            ClientSecret = ConfigurationManager.AppSettings["ClientSecret"],
            GrantType = ConfigurationManager.AppSettings["GrantType"],
            Resource = ConfigurationManager.AppSettings["Resource"],
            LogoutUri = ConfigurationManager.AppSettings["LogoutUri"],
            GraphAPIUri = ConfigurationManager.AppSettings["GraphAPIUri"];

        /// <summary>
        /// Get office-365 login url
        /// </summary>
        /// <returns>string value</returns>
        public static string GetLoginUrl()
        {
            IDictionary<string, string> urlParams = new Dictionary<string, string>();
            urlParams["client_id"] = ClientId;
            urlParams["response_type"] = ResponseType;
            urlParams["redirect_uri"] = RedirectUri;
            urlParams["response_mode"] = ResponseMode;
            urlParams["state"] = Guid.NewGuid().ToString();

            string url = $"{AzureADInstance}/{DirectoryId}/oauth2/authorize";

            return CombineUrl(url, urlParams);
        }

        /// <summary>
        /// Get access token to fetch the office 365 account info
        /// </summary>
        /// <param name="code">code sent by microsoft to fetch the access token</param>
        /// <returns>AccessTokenResponseModel</returns>
        public static AccessTokenResponseModel GetAccessToken(string code)
        {
            string url = $"{AzureADInstance}/{DirectoryId}/oauth2/token";
            var postData = new StringBuilder();
            AppendUrlEncoded(postData, "grant_type", GrantType);
            AppendUrlEncoded(postData, "client_id", ClientId);
            AppendUrlEncoded(postData, "code", code);
            AppendUrlEncoded(postData, "redirect_uri", RedirectUri);
            AppendUrlEncoded(postData, "client_secret", ClientSecret);
            AppendUrlEncoded(postData, "resource", Resource);

            string result = MakeWebRequest(url, EnumUtils.RequestMethod.POST, postData.ToString(), "application/x-www-form-urlencoded");

            return JsonConvert.DeserializeObject<AccessTokenResponseModel>(result);
        }

        /// <summary>
        /// Get claim data from jwt token fetched using GetAccessToken method
        /// </summary>
        /// <param name="idToken"></param>
        /// <returns></returns>
        public static IDictionary<string, string> GetClaimData(string idToken)
        {
            var jwtToken = new JwtSecurityToken(idToken);
            IDictionary<string, string> claimsData = new Dictionary<string, string>();
            foreach (var claim in jwtToken.Claims)
            {
                claimsData.Add(claim.Type, claim.Value);
            }

            return claimsData;
        }

        /// <summary>
        /// Get office-365 logout url
        /// </summary>
        /// <returns></returns>
        public static string GetLogoutUrl()
        {
            IDictionary<string, string> urlParams = new Dictionary<string, string>();
            urlParams["post_logout_redirect_uri"] = LogoutUri;
            string url = $"{AzureADInstance}/Common/oauth2/logout";

            return CombineUrl(url, urlParams);
        }

        public static AccountInfoModel GetAccountInfo(string accessToken)
        {
            IDictionary<string, string> headers = new Dictionary<string, string>();
            headers["Authorization"] = $"Bearer {accessToken}";
            string result = MakeWebRequest($"{GraphAPIUri}/me", EnumUtils.RequestMethod.GET, requestHeaders: headers);

            return JsonConvert.DeserializeObject<AccountInfoModel>(result);
        }

        private static void AppendUrlEncoded(StringBuilder sb, string name, string value)
        {
            if (sb.Length != 0)
                sb.Append("&");
            sb.Append(HttpUtility.UrlEncode(name));
            sb.Append("=");
            sb.Append(HttpUtility.UrlEncode(value));
        }

       /// <summary>
        /// Generates url with query string parameters.
        /// </summary>
        /// <param name="methodName">method name of apersonal api.</param>
        /// <param name="queryStringParams">parameters to append in querystring of url.</param>
        /// <returns>string value</returns>
        public static string CombineUrl(string url, IDictionary<string, string> queryStringParams)
        {
            if (!url.EndsWith("?"))
            {
                url = url + "?";
            }
            List<string> parametersList = new List<string>();
            foreach (var parameter in queryStringParams)
            {
                parametersList.Add(parameter.Key + "=" + parameter.Value);
            }
            url = url + string.Join("&", parametersList);

            return url;
        }

        /// <summary>
        /// Calling 3rd party web apis.
        /// </summary>
        /// <param name="destinationUrl">destination url of API or service</param>
        /// <param name="methodName"></param>
        /// <param name="requestJSON"></param>
        /// <returns></returns>
        public static string MakeWebRequest(string destinationUrl, RequestMethod methodName, string requestJSON = "", string contentType = "application/json", IDictionary<string, string> requestHeaders = null)
        {
            try
            {
                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(destinationUrl);
                request.Method = methodName.ToString();

                if (requestHeaders != null)
                {
                    foreach (var header in requestHeaders)
                    {
                        request.Headers.Add(header.Key, header.Value);
                    }
                }

                if (methodName == EnumUtils.RequestMethod.POST)
                {
                    if (!string.IsNullOrEmpty(requestJSON))
                    {
                        byte[] bytes = System.Text.Encoding.ASCII.GetBytes(requestJSON);
                        request.ContentType = contentType;
                        request.ContentLength = bytes.Length;
                        using (Stream requestStream = request.GetRequestStream())
                        {
                            requestStream.Write(bytes, 0, bytes.Length);
                        }
                    }
                }
                using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
                {
                    if (response.StatusCode == HttpStatusCode.OK || response.StatusCode == HttpStatusCode.Created)
                    {
                        using (StreamReader reader = new StreamReader(response.GetResponseStream()))
                        {
                            return reader.ReadToEnd();
                        }
                    }
                }

                return null;
            }
            catch (WebException webEx)
            {
                if (webEx.Response != null)
                {
                    var response = (HttpWebResponse)webEx.Response;
                    using (StreamReader reader = new StreamReader(response.GetResponseStream()))
                    {
                        return reader.ReadToEnd();
                    }
                }
                else
                {
                    return webEx.Message;
                }
            }
        }

        /// <summary>
        /// Encrypt and save data in cookies usin FormsAuthentication.
        /// </summary>
        /// <param name="model">Data to save in cookie</param>
        public static void SetCookiesData(AccountInfoModel model)
        {
            //ResetCookieName(model.Email);
            FormsAuthentication.SetAuthCookie(model.Email, false, FormsAuthentication.FormsCookiePath);
            var userData = JsonConvert.SerializeObject(model);
            int timeout = model.RememberMe ? 525600 : 20; // 525600 min = 1 year
            FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1,
                model.Email,
                DateTime.Now,
                DateTime.Now.AddMinutes(timeout),
                true,
                userData,
                FormsAuthentication.FormsCookiePath);
            string encTicket = FormsAuthentication.Encrypt(ticket);
            HttpContext.Current.Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encTicket));
            // Create the cookie for apersona data.
            string cookieName = GenerateCookieName(model.Mail);
            cookieName = Crypto.MD5Hash(cookieName);
            var cookie = new HttpCookie(cookieName, encTicket);
            cookie.Expires = DateTime.Now.AddMinutes(timeout);
            cookie.HttpOnly = true;
            HttpContext.Current.Response.Cookies.Add(cookie);
        }

        /// <summary>
        /// Decrypt the cookie value for a logged in user
        /// </summary>
        /// <returns>LoginResponseModel</returns>
        public static AccountInfoModel DecryptCookie()
        {
            var httpCookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];

            if (httpCookie == null)
            {
                return null;
            }
            var decryptedCookie = FormsAuthentication.Decrypt(httpCookie.Value);

            if (decryptedCookie == null)
            {
                return null;
            }

            return JsonConvert.DeserializeObject<AccountInfoModel>(decryptedCookie.UserData);
        }
    }

Step 4: Now consume above "AADAuthUtils.cs" class's methods in your controller class as following:
        /// <summary>
        /// Redirects to microsoft's login portal
        /// </summary>
        /// <param name="authParam2"></param>
        /// <returns>Redirects</returns>
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult AADLogin()
        {
            string loginUrl = AADAuthUtils.GetLoginUrl();
            return Redirect(loginUrl);
        }
        /// <summary>
        /// Callback function to get the account information of user after successfully authenticated using Azure AD.
        /// </summary>
        /// <returns>Redirects</returns>
        public ActionResult AADLoginCallback()
        {
            if (string.IsNullOrWhiteSpace(Request["code"]))
            {
                return RedirectToLogin("Error occurred!");
            }
            else
            {
                // Get access token using the code send by microsoft.
                AccessTokenResponseModel accessTokenRes = AADAuthUtils.GetAccessToken(Request["code"]);
                if (!string.IsNullOrWhiteSpace(accessTokenRes.Error))
                {
                    return RedirectToLogin(accessTokenRes.Error_Description);
                }
                // Get the account info using access token.
                var accountInfo = AADAuthUtils.GetAccountInfo(accessTokenRes.Access_Token);
                if (accountInfo.Error != null)
                {
                    return RedirectToLogin($"{accountInfo.Error.Code}: {accountInfo.Error.Message}");
                }
               
                AADAuthUtils.SetCookiesData(accountInfo);
                return Redirect("/");
            }
        }
        private ActionResult RedirectToLogin(string error)
        {
            TempData["AADLoginErr"] = error;
            return RedirectToAction("Login");
        }

       /// <summary>
        /// Logout
        /// </summary>
        /// <returns>View</returns>
        [Authorize]
        [HttpGet]
        public ActionResult Logout()
        {
            FormsAuthentication.SignOut();
            Session.Remove("asmAPIOpts");
            return RedirectToAction("Login", "User");
        }

Step 5: In this tutorial I am using the forms authentication to authorize the users. To enable forms authentication add the following line in your web.config file:
 <system.web>
    <authentication mode="Forms">
      <forms cookieless="UseCookies" loginUrl="~/user/login" slidingExpiration="true"></forms>
    </authentication>
 </system.web>

Step 6: Add following html in your .cshtml view, where you want to add the login functionality:
<div class="form-horizontal">
    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            @using (Html.BeginForm("AADLogin", "User", FormMethod.Post))
            {
                @Html.AntiForgeryToken()
            <div style="display:inline-grid;">
                <p style="font-weight:bold;">OR</p>
                <input type="submit" value="Login with Office365" class="btn btn-danger" />
            </div>
                <input type="hidden" id="authParam2" name="authParam2" />
            }
            <p>
                <h5 style="color:red;">@TempData["AADLoginErr"]</h5>
            </p>
        </div>
    </div>
</div>

Step 7: You can add following code in layout view where you want to show the logout button:
@using RegistrationAndLogin.Utils;
@{
    ViewBag.Title = "Portal";
    string logoutUrl = AADAuthUtils.GetLogoutUrl();
    var loginInfo = AppUtility.DecryptCookie();
}

<h2>Secured Portal</h2>

Welcome @HttpContext.Current.User.Identity.Name

@if (Request.IsAuthenticated)
{
    if (loginInfo.IsExternalLogin)
    {
        <p><a href="https://portal.office.com" target="_blank">Go to Office 365</a></p>
        <a href="@logoutUrl">Logout from office 365</a>
    }
    else
    {
        @Html.ActionLink("Logout", "Logout", "User")
    }
}

Step 8: Add following some required classes I am using to get the response from azure AD:
public class AccessTokenResponseModel
    {
        public string Access_Token { get; set; }
        public string Token_Type { get; set; }
        public string Expires_In { get; set; }
        public string Expires_On { get; set; }
        public string Resource { get; set; }
        public string Refresh_Token { get; set; }
        public string Scope { get; set; }
        public string Id_Token { get; set; }

        #region Error
        public string Error { get; set; }
        public string Error_Description { get; set; }
        #endregion
    }

 public class AccountInfoModel
    {
        public string Id { get; set; }
        public List<string> BusinessPhones { get; set; }
        public string DisplayName { get; set; }
        public object GivenName { get; set; }
        public object JobTitle { get; set; }
        public string Mail { get; set; }
        public string MobilePhone { get; set; }
        public string OfficeLocation { get; set; }
        public string PreferredLanguage { get; set; }
        public string SurName { get; set; }
        public string UserPrincipalName { get; set; }
        public ErrorResponse Error { get; set; }
        public class ErrorResponse
        {
            public string Code { get; set; }
            public string Message { get; set; }
        }
    }

1 comment:

  1. .Net: Implementing Authentication Using Azure Active Directory And Microsoft Office 365. >>>>> Download Now

    >>>>> Download Full

    .Net: Implementing Authentication Using Azure Active Directory And Microsoft Office 365. >>>>> Download LINK

    >>>>> Download Now

    .Net: Implementing Authentication Using Azure Active Directory And Microsoft Office 365. >>>>> Download Full

    >>>>> Download LINK 2L

    ReplyDelete