web-dev-qa-db-fra.com

Où stocker le jeton du porteur dans MVC à partir de l'API Web

Scénario

J'ai une API Web ASP.NET qui utilise le flux de mot de passe OAuth pour fournir des jetons au porteur pour accéder à ses ressources.

Je suis en train de créer une application MVC qui devra utiliser cette API.

Il est prévu que les contrôleurs MVC appellent l'API au nom du navigateur client.

Les requêtes ajax du navigateur atteindront les contrôleurs MVC, puis les appels API seront effectués. Les résultats sont ensuite renvoyés au client sous forme de JSON et traités en Java-script.

Le client ne doit jamais communiquer directement avec l'API.

Se authentifier.

Je dois trouver le meilleur moyen de gérer le jeton porteur une fois qu'il a été reçu dans l'application MVC via un appel réussi au point de terminaison du jeton de l'API Web.

Je dois utiliser ce jeton au porteur dans tous les appels ultérieurs à l'API.

Mon plan est de le stocker dans le System.Web.HttpContext.Current.Session["BearerToken"]

Je peux ensuite créer un AuthorizationAttribute personnalisé qui vérifiera si un BearerToken est présent dans le HttpContext actuel, s'il n'est pas présent, le client devra revoir le point de terminaison du jeton.

Est-ce que cela semble faisable?

Je demande l'avis des gens à ce sujet car je ne suis pas convaincu que ce soit la meilleure solution pour mon projet.

18
Derek

J'ai réussi à trouver quelque chose qui, je pense, fonctionnera très bien.

J'utilise le middleware Owin pour l'authentification des cookies.

Dans l'application MVC, j'ai un fichier de démarrage Owin dans lequel l'authentification par cookie est configurée: -

 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"),

            });
        }
    }

J'ai ensuite créé un AccountController avec deux méthodes d'action pour la connexion et la déconnexion: -

Connexion.

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

Déconnexion

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

                return RedirectToAction("Login");
            }

Protéger les ressources

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

    }
19
Derek

Puisque vous avez mentionné que vous utilisez HttpClient (). J'ai fait une chose similaire en utilisant HttpClient () -

Obtenir un jeton -

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

tilisez le jeton pour publier des données

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

Même si vous stockez le jeton Bearer dans HttpContext, vous devrez prendre soin de l'heure d'expiration du jeton définie dans l'API Web. La validation de l'existence du jeton uniquement dans la session n'aidera pas car l'ancien jeton sera invalide après l'heure d'expiration.

2
Souvik Ghosh