Quantcast
Channel: ASP.NET Core
Viewing all articles
Browse latest Browse all 9386

Possible for user to intercept access token (for calling Microsoft Graph API) when user causes .NET Core 2.0 app (ASP.NET Core) to call an application service?

$
0
0

Is it possible for a user to intercept an access token (for calling the Microsoft Graph API) when the user causes a .NET Core 2.0 app (ASP.NET Core) to call an application service? The service is specifically an email service that is called in various actions that can be carried out by the user. The following is my "IEmailSender" (user-defined) interface:

using System.Threading.Tasks;

namespace GlobalWarmingCenter.Services
{
    public interface IEmailSender
    {
        Task SendEmailAsync(string email, string subject, string message);
    }
}

My app has an "AuthMessageSender" (user-defined) class that implements "IEmailSender" (user-defined). This is where an access token is generated to call the Microsoft Graph API:

using Microsoft.Identity.Client;
using System.Threading.Tasks;
using GlobalWarmingCenter.Models;
using Microsoft.Extensions.Options;
using Microsoft.Graph;
using System;
using System.Globalization;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;

namespace GlobalWarmingCenter.Services
{
    // This class is used by the application to send Email and SMS
    // when you turn on two-factor authentication in ASP.NET Identity.
    // For more details see this link https://go.microsoft.com/fwlink/?LinkID=532713
    public class AuthMessageSender : IEmailSender, ISmsSender
    {
        private readonly AzureAd _azuread;
        private readonly OAuth2 _oauth2;
        private readonly PointOfContact _pointOfContact;

        public AuthMessageSender(IOptions<AzureAd> AzureAd,
            IOptions<OAuth2> OAuth2, IOptions<PointOfContact> PointOfContact)
        {
            _azuread = AzureAd.Value;
            _oauth2 = OAuth2.Value;
            _pointOfContact = PointOfContact.Value;
        }

        public async Task SendEmailAsync(string email, string subject, string message)
        {

            string accessToken = await GetAccessToken();

            string messagetemplate = @"{{""Message"": {{""Subject"": ""{0}"",""Body"": {{""ContentType"": ""HTML"",""Content"": ""{1}""
                    }},""ToRecipients"": [
                    {{""EmailAddress"": {{""Address"": ""{2}""
                        }}
                    }}
                    ]
                }},""SaveToSentItems"": ""true""
            }}";

            HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "https://graph.microsoft.com/v1.0/users/" +
                _pointOfContact.EmailAddress + "/sendMail");

            string formattedMessage = String.Format(messagetemplate, subject, message, email);

            request.Content = new StringContent(formattedMessage, Encoding.UTF8, "application/json");
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

            GraphServiceClient client = new GraphServiceClient(
                new DelegateAuthenticationProvider(
                    (requestMessage) =>
                    {
                        requestMessage.Headers.Authorization =
                            new AuthenticationHeaderValue("Bearer", accessToken);

                        requestMessage.Headers.Add("Content-Type", "application/json");

                        return Task.FromResult(0);
                    }));

            try
            {
                HttpResponseMessage sendResults = await client.HttpProvider.SendAsync(request);

                if (sendResults.IsSuccessStatusCode)
                {

                }
            }
            catch
            {
                throw;
            }

            /*
            // If the token we used was insufficient to make the query, drop the token from the cache.
            // The Users page of the website will show a message to the user instructing them to grant
            // permissions to the app (see User/Index.cshtml).
            if (response.StatusCode == System.Net.HttpStatusCode.Forbidden)
            {
                // BUG: Here, we should clear MSAL's app token cache to ensure that on a subsequent call
                // to SyncController, MSAL does not return the same access token that resulted in this 403.
                // By clearing the cache, MSAL will be forced to retrieve a new access token from AAD,
                // which will contain the most up-to-date set of permissions granted to the app. Since MSAL
                // currently does not provide a way to clear the app token cache, we have commented this line
                // out. Thankfully, since this app uses the default in-memory app token cache, the app still
                // works correctly, since the in-memory cache is not persistent across calls to SyncController
                // anyway. If you build a persistent app token cache for MSAL, you should make sure to clear
                // it at this point in the code.
                //
                //daemonClient.AppTokenCache.Clear(Startup.clientId);
            }
            if (!response.IsSuccessStatusCode)
            {
                throw new HttpResponseException(response.StatusCode);
            }

            // Plug in your email service here to send an email.
            //  await Task.FromResult(0);
            */
        }

        public async Task<string> GetAccessToken()
        {
            string accessToken = null;

            ConfidentialClientApplication daemonClient = new ConfidentialClientApplication(_azuread.ClientId,
                string.Format(CultureInfo.InvariantCulture,
                    _azuread.AadInstance,
                    _azuread.Tenant,
                    "/v2.0"),
                _oauth2.RedirectUri,
                new ClientCredential(_azuread.ClientSecret),
                null,
                new TokenCache());
            AuthenticationResult authResult = await daemonClient.AcquireTokenForClientAsync(new string[]
            {"https://graph.microsoft.com/.default"
            });
            if (authResult != null)
            {
                accessToken = authResult.AccessToken;
            }

            return accessToken;
        }

        public Task SendSmsAsync(string number, string message)
        {
            // Plug in your SMS service here to send a text message.
            return Task.FromResult(0);
        }
    }
}

The following code is part of a controller in my app that initializes a private variable in the constructor with the value of the "IEmailSender" parameter and calls "SendEmailAsync" in one of its actions "ChangeUserName", for instance:

using System;
using System.Linq;
using System.Net.Http;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using GlobalWarmingCenter.Models;
using GlobalWarmingCenter.Models.ManageViewModels;
using GlobalWarmingCenter.Services;

namespace GlobalWarmingCenter.Controllers { [Authorize] public class ManageController : Controller { private readonly UserManager<ApplicationUser> _userManager; private readonly SignInManager<ApplicationUser> _signInManager; // private readonly string _externalCookieScheme; private readonly IEmailSender _emailSender; // private readonly ISmsSender _smsSender; private readonly PointOfContact _pointOfContact; private readonly ReCaptcha _reCaptcha; private readonly ILogger _logger; public ManageController( UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager, IOptions<ReCaptcha> ReCaptcha, // IOptions<IdentityCookieOptions> identityCookieOptions, IEmailSender emailSender, // ISmsSender smsSender, IOptions<PointOfContact> PointOfContact, ILoggerFactory loggerFactory) { _userManager = userManager; _signInManager = signInManager; // _externalCookieScheme = identityCookieOptions.Value.ExternalCookieAuthenticationScheme; _emailSender = emailSender; // _smsSender = smsSender; _pointOfContact = PointOfContact.Value; _reCaptcha = ReCaptcha.Value; _logger = loggerFactory.CreateLogger<ManageController>(); } // // GET: /Manage/ChangeUserName [HttpGet] public async Task<IActionResult> ChangeUserName() { ApplicationUser user = await GetCurrentUserAsync(); ViewData["CurrentUserName"] = user.UserName; return View(); } // // POST: /Manage/ChangeUserName [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> ChangeUserName(ChangeUserNameViewModel model) { var user = await GetCurrentUserAsync(); ViewData["CurrentUserName"] = user.UserName; if (!ModelState.IsValid) { return View(model); } CaptchaVerification captchaResult = await VerifyCaptcha(model.ReCaptcha); if (!captchaResult.Success) { ModelState.AddModelError("", "Captcha is not valid"); return View(model); } if (user != null) { var result = await _userManager.SetUserNameAsync(user, model.NewUserName); if (result.Succeeded) { await _signInManager.SignInAsync(user, isPersistent: false); _logger.LogInformation(3, "User changed their UserName successfully."); var code = await _userManager.GeneratePasswordResetTokenAsync(user); var callbackUrl = Url.Action(nameof(AccountController.ResetPassword), "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme); string emailSubjectToVisitor = "Your globalwarming.center account UserName has been changed"; string emailMessageToVisitor = "<p>Dear " + user.UserName + ":</p><p>" +"Your globalwarming.center account UserName has been changed from " + ViewData["CurrentUserName"] as string + " to " + user.UserName + ".</p><p>If you did " +"not change your UserName, someone else may have access to your account. You should " +"immediately reset your password by clicking this " +$"<a href='{callbackUrl}'>link</a> and filling out the form. That way, you can " +"change your password and prevent anyone else from logging into your " +"account. Then, you should log into your account and change " +"your UserName, if necessary.</p><p>" +"Sincerely," + "<br>" + _pointOfContact.FullName + "<br>" + _pointOfContact.Title + "</p>"; await _emailSender.SendEmailAsync(user.Email, emailSubjectToVisitor, emailMessageToVisitor); return RedirectToAction(nameof(Index), new { Message = ManageMessageId.ChangeUserNameSuccess }); } AddErrors(result); return View(model); } return RedirectToAction(nameof(Index), new { Message = ManageMessageId.Error }); } // verify the captcha's response with Google private async Task<CaptchaVerification> VerifyCaptcha(string captchaResponse) { string userIP = string.Empty; var ipAddress = Request.HttpContext.Connection.RemoteIpAddress; if (ipAddress != null) { userIP = ipAddress.MapToIPv4().ToString(); } var payload = string.Format("&secret={0}&response={1}&remoteip={2}", _reCaptcha.Secret, captchaResponse, userIP ); var client = new HttpClient { BaseAddress = new Uri("https://www.google.com") }; var request = new HttpRequestMessage(HttpMethod.Post, "/recaptcha/api/siteverify") { Content = new StringContent(payload, Encoding.UTF8, "application/x-www-form-urlencoded") }; var response = await client.SendAsync(request); var serializer = new DataContractJsonSerializer(typeof(CaptchaVerification)); return serializer.ReadObject(response.Content.ReadAsStreamAsync().Result) as CaptchaVerification; } #region Helpers private void AddErrors(IdentityResult result) { foreach (var error in result.Errors) { ModelState.AddModelError(string.Empty, error.Description); } } public enum ManageMessageId { AddPhoneSuccess, AddLoginSuccess, ChangeEmailSuccess, ChangeUserNameSuccess, ChangePasswordSuccess, SetTwoFactorSuccess, // SetPasswordSuccess, RemoveLoginSuccess, RemovePhoneSuccess, Error } private Task<ApplicationUser> GetCurrentUserAsync() { // return _userManager.GetUserAsync(HttpContext.User); string email = _userManager.GetUserName(User); return _userManager.FindByNameAsync(_userManager.GetUserName(User)); } #endregion } }

Again, do you see any possibilities of an access token potentially getting stolen by a hacker?

Reference: https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-protocols-oauth-client-creds#get-a-token


Viewing all articles
Browse latest Browse all 9386

Trending Articles