Where to store Bearer Token in MVC from Web API

45,601

Solution 1

I've managed to come up with something that i think will work quite well.

I'm using the Owin Middleware for Cookie Authentication.

Within the MVC Application i have an Owin Startup file where the Cookie Authentication is configured :-

 public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            // For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=316888

            app.UseCookieAuthentication(new CookieAuthenticationOptions()
            {
                AuthenticationType = "ApplicationCookie",
                LoginPath = new PathString("/Account/Login"),

            });
        }
    }

I then made an AccountController with two Action methods for Logging In and Logging out :-

Logging In.

public ActionResult Login(LoginModel model,string returnUrl)
        {
            var getTokenUrl = string.Format(ApiEndPoints.AuthorisationTokenEndpoint.Post.Token, ConfigurationManager.AppSettings["ApiBaseUri"]);

            using (HttpClient httpClient = new HttpClient())
            {
                HttpContent content = new FormUrlEncodedContent(new[]
                {
                    new KeyValuePair<string, string>("grant_type", "password"), 
                    new KeyValuePair<string, string>("username", model.EmailAddress), 
                    new KeyValuePair<string, string>("password", model.Password)
                });

                HttpResponseMessage result = httpClient.PostAsync(getTokenUrl, content).Result;

                string resultContent = result.Content.ReadAsStringAsync().Result;

                var token = JsonConvert.DeserializeObject<Token>(resultContent);

                AuthenticationProperties options = new AuthenticationProperties();

                options.AllowRefresh = true;
                options.IsPersistent = true;
                options.ExpiresUtc = DateTime.UtcNow.AddSeconds(int.Parse(token.expires_in));

                var claims = new[]
                {
                    new Claim(ClaimTypes.Name, model.EmailAddress),
                    new Claim("AcessToken", string.Format("Bearer {0}", token.access_token)),
                };

                var identity = new ClaimsIdentity(claims, "ApplicationCookie");

                Request.GetOwinContext().Authentication.SignIn(options, identity);

            }

            return RedirectToAction("Index", "Home");
        }

Logging Out

  public ActionResult LogOut()
            {
                Request.GetOwinContext().Authentication.SignOut("ApplicationCookie");

                return RedirectToAction("Login");
            }

Protecting the Resources

    [Authorize]
    public class HomeController : Controller
    {

        private readonly IUserSession _userSession;

        public HomeController(IUserSession userSession)
        {
            _userSession = userSession;
        }

        // GET: Home
        public ActionResult Index()
        {

            ViewBag.EmailAddress = _userSession.Username;
            ViewBag.AccessToken = _userSession.BearerToken;

            return View();
        }
    }


 public interface IUserSession
    {
        string Username { get; }
        string BearerToken { get; }
    }

public class UserSession : IUserSession
    {

        public string Username
        {
            get { return ((ClaimsPrincipal)HttpContext.Current.User).FindFirst(ClaimTypes.Name).Value; }
        }

        public string BearerToken
        {
            get { return ((ClaimsPrincipal)HttpContext.Current.User).FindFirst("AcessToken").Value; }
        }

    }

Solution 2

Since you have mentioned you are using HttpClient(). I did a similar thing using HttpClient()-

Get token-

    static Dictionary<string, string> GetTokenDetails(string userName, string password)
    {
        Dictionary<string, string> tokenDetails = null;
        try
        {
            using (var client = new HttpClient())
            {
                var login = new Dictionary<string, string>
                   {
                       {"grant_type", "password"},
                       {"username", userName},
                       {"password", password},
                   };

                var resp = client.PostAsync("http://localhost:61086/token", new FormUrlEncodedContent(login));
                resp.Wait(TimeSpan.FromSeconds(10));

                if (resp.IsCompleted)
                {
                    if (resp.Result.Content.ReadAsStringAsync().Result.Contains("access_token"))
                    {
                        tokenDetails = JsonConvert.DeserializeObject<Dictionary<string, string>>(resp.Result.Content.ReadAsStringAsync().Result);
                    }
                }
            }
        }
        catch (Exception ex)
        {

        }
        return tokenDetails;
    }

Use the token to Post data

static string PostData(string token, List<KeyValuePair<string, string>> lsPostContent)
{
    string response = String.Empty;
    try
    {
        using (var client = new HttpClient())
        {
            FormUrlEncodedContent cont = new FormUrlEncodedContent(lsPostContent);
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
            var resp = client.PostAsync("https://localhost:61086/api/<your API controller>/", cont);

            resp.Wait(TimeSpan.FromSeconds(10));

            if (resp.IsCompleted)
            {
                if (resp.Result.StatusCode == HttpStatusCode.Unauthorized)
                {
                    Console.WriteLine("Authorization failed. Token expired or invalid.");
                }
                else
                {
                    response = resp.Result.Content.ReadAsStringAsync().Result;
                    Console.WriteLine(response);
                }
            }
        }
    }
    catch (Exception ex)
    {

    }
    return response;
}

Even if you store the Bearer token in HttpContext, you will need to take care of the token expiry time which is set in the Web API. Validating the existence of token just in the session won't help since the old token will be invalid after the expiry time.

Share:
45,601

Related videos on Youtube

Derek
Author by

Derek

Currently working as a software developer building enterprise solutions within the .Net Framework I have an interest in design patterns, best practice and new technologies.

Updated on August 17, 2022

Comments

  • Derek
    Derek over 1 year

    Scenario

    I have an ASP.NET Web API that uses the OAuth Password Flow to provide Bearer Tokens to gain access to its resources.

    I'm now in the process of making an MVC app that will need to use this API.

    The plan is to have the MVC controllers make calls to the API on behalf of the client browser.

    The ajax requests from the browser will hit the MVC controllers and then the API calls are made. Results are then fed back to the client as JSON and handles in java-script.

    The client should never communicate directly with the API.

    Getting Authenticated.

    I need to find the best way to handle the Bearer Token once it has been received in the MVC app via a successful call to the web api token endpoint.

    I need to use this bearer token in any subsequent calls to the api.

    My plan is to store it in the System.Web.HttpContext.Current.Session["BearerToken"]

    I can then create a custom AuthorizationAttribute that will check to see if a BearerToken is present in the current HttpContext, if it is not present, the client will need to revisit the token endpoint.

    Does this seem feasible?

    I'm asking for peoples opinion on this as I am not convinced this the best solution for my project.

    • Souvik Ghosh
      Souvik Ghosh over 7 years
      Bearer token would go in your Request headers. I believe you are accessing the Web API via AJAX call. So, you have to create the AJAX request appropriately with the token that you have received from the Web API. I did a similar thing here but without AJAX- stackoverflow.com/questions/38661090/…
    • Derek
      Derek over 7 years
      Im making the wep api call using a HttpClient object in the MVC app
  • Velkumar
    Velkumar almost 7 years
    I'm getting userSession object as NULL on my HomeController/Index
  • Derek
    Derek almost 7 years
    userSession will be null if you are not using dependency injection in your application.
  • Velkumar
    Velkumar almost 7 years
    @Derek, yes you are right can you help me to resolve that?
  • Derek
    Derek almost 7 years
    that is an entirely different topic. Research Autofac library. Its easy to implement!
  • Minus
    Minus about 6 years
    Nice way to handle login, I still wonder how did you manage the "expired Access_token"? What I understand is cookie authentication will keep you logged in even if the token expired. Next API call, you'll get a fresh new token. And you'll need to replace it. How would you handle that? Also what if that Access_token sets the user being logged in or out with the scope of MVC app?
  • Derek
    Derek about 6 years
    Its been along time since i did this, but i think im setting the expiration of the cookie, to the expiration of the access_token, as you can see in the code above. So the user will be forced to re-authenticate in the MVC app and the access token will be re-hydrated on login.