AngularJS - Resto + Servicio de autenticación -- javascript campo con angular.js camp codereview Relacionados El problema

AngularJS - REST + Authentication service


15
vote

problema

Español

Tengo un servicio web de reposo que usa oauth 2 para autenticar y autorizar las solicitudes.

Tengo un punto final, que al recibir las credenciales correctas, responde con un token de acceso que se utilizará en las solicitudes consiguientes.

Esto evita persistir una sesión y usar cookies, que es lo que queríamos lograr cuando comenzamos a diseñar nuestro servicio web.

junto con el token de acceso También se recibe un token de solicitud de la respuesta. Este token de actualización se utiliza para obtener un nuevo token de acceso cuando el original ha caducado.

Con TimesPan corto y token Actualización, un usuario no puede autenticarse como otro usuario simplemente al robar el token de acceso (teóricamente, pero por un corto período de tiempo, es decir, hasta que se actualice).

He creado el siguiente proveedor para Angularjs:

  var restProvider = function () {       var _enabled = true;      var _authUrl = '';      var _clientId = '';      var _sessionLife;       // Returns whether the service is available or not      var _supported = function () {          return _enabled && _authUrl;      }       var _fetchTokens = function (http, deferred, url, credentials, storage) {           credentials.client_id = _clientId;           credentials.grant_type = 'password';            http.post(url, credentials, {               headers: {                  'Content-Type': 'application/x-www-form-urlencoded'               },               transformRequest: function (data, headers) {                  var str = [];                  for(var p in data)                     str.push(encodeURIComponent(p) + '=' + encodeURIComponent(data[p]));                  return str.join('&');               }           }).then(function (successResponse) {                storage.save('access_token', successResponse.data.access_token, {                   expiration: successResponse.data.expires_in;                });                storage.save('refresh_token', successResponse.data.refresh_token, {                   expiration: _sessionLife;                });                storage.save('token_type', successResponse.data.token_type);                 deferred.resolve(successResponse);           }, function (errorResponse) {                deferred.reject(errorResponse);           });      }       return {          //The following methods are for configuration only          //If no arguments are provided, returns the property's value          //If arguments are provided, returns "this" for chaining multiple methods          //val: Boolean          enabled: function (val) {              if(val === undefined || val === null) return _enabled;              _enabled = val;              return this;          },           //endpoint: String          authorizationEndpoint: function (endpoint) {              if(!endpoint) return _authUrl;              _authUrl = endpoint;              return this;          },           //clientId: String          client: function (clientId) {             if(!clientId) return _clientId;             _clientId = clientId;             return this;          },           //seconds: Number          sessionLife: function (seconds) {             if(!seconds) return _sessionLife;             _sessionLife = seconds;             return this;          },           //"storage" and "logger" are injected services of mine          $get: ['$http', '$q', 'storage', 'logger', function ($http, $q, storage, logger) {               return {                    signIn: function (username, password) {                        var deferred = $q.defer();                        if(username && password && _supported())                            _fetchTokens($http, deferred, _authUrl, { username: username, password: password }, storage);                        else                            deferred.reject();                        return deferred.promise;                    }               }          }      } }   

Como puede ver, es un código muy largo y incluso está incompleto, esta es solo la función de autenticación.

_fetchTokens hace una solicitud de publicación al punto final de OAURH de My Web Service y recupera los tokens que forman la respuesta. Luego procede a persistirlos (por ejemplo ,Storage local) para que puedan usarse para solicitudes posteriores y continuar viviendo incluso si la aplicación está cerrada.

signIn es la función pública que el cliente verá desde afuera. Simplemente crea el objeto diferido y llama _fetchTokens .

Mi problema es que las solicitudes consiguientes deben enviar el token de acceso (en el encabezado de autorización). Si se envía una solicitud con un token caducado, se devuelve un 401 no autorizado , por lo que este servicio debe proceder para tratar de actualizar el token. Después de refrescar con éxito el token, volverá a volver a intentar la solicitud original, pero con el nuevo token de acceso.

Esto significa que una línea simple de código del cliente:

  service.get('api.example.com/products');   

debe hacer todo esto detrás de las escenas.

Con este enfoque, creo que habrá un gran código replicado, por lo que estaba buscando ayuda sobre cómo estructurar mejor mi código. No solo para una mejor calidad sino también para una mejor legibilidad.

Supongo que ese servicio no es tan raro, así que espero que muchas personas hayan experimentado este tipo de situaciones.

Original en ingles

I have a REST web service that uses OAuth 2 for authenticating and authorizing requests.

I have an endpoint, that when receiving the correct credentials, responds with an access token that will be used in consequent requests.

This avoids persisting a session and using cookies, which is what we wanted to accomplish when we started designing our web service.

Along with the access token a request token is also received from the response. This refresh token is used for getting a new access token when the original one has expired.

With short timespan and token refreshing, an user can't authenticate as another user just by stealing the access token (theorically it can, but for a short period of time, i.e. until it's refreshed).

I have created the following provider for AngularJS:

var restProvider = function () {       var _enabled = true;      var _authUrl = '';      var _clientId = '';      var _sessionLife;       // Returns whether the service is available or not      var _supported = function () {          return _enabled && _authUrl;      }       var _fetchTokens = function (http, deferred, url, credentials, storage) {           credentials.client_id = _clientId;           credentials.grant_type = 'password';            http.post(url, credentials, {               headers: {                  'Content-Type': 'application/x-www-form-urlencoded'               },               transformRequest: function (data, headers) {                  var str = [];                  for(var p in data)                     str.push(encodeURIComponent(p) + '=' + encodeURIComponent(data[p]));                  return str.join('&');               }           }).then(function (successResponse) {                storage.save('access_token', successResponse.data.access_token, {                   expiration: successResponse.data.expires_in;                });                storage.save('refresh_token', successResponse.data.refresh_token, {                   expiration: _sessionLife;                });                storage.save('token_type', successResponse.data.token_type);                 deferred.resolve(successResponse);           }, function (errorResponse) {                deferred.reject(errorResponse);           });      }       return {          //The following methods are for configuration only          //If no arguments are provided, returns the property's value          //If arguments are provided, returns "this" for chaining multiple methods          //val: Boolean          enabled: function (val) {              if(val === undefined || val === null) return _enabled;              _enabled = val;              return this;          },           //endpoint: String          authorizationEndpoint: function (endpoint) {              if(!endpoint) return _authUrl;              _authUrl = endpoint;              return this;          },           //clientId: String          client: function (clientId) {             if(!clientId) return _clientId;             _clientId = clientId;             return this;          },           //seconds: Number          sessionLife: function (seconds) {             if(!seconds) return _sessionLife;             _sessionLife = seconds;             return this;          },           //"storage" and "logger" are injected services of mine          $get: ['$http', '$q', 'storage', 'logger', function ($http, $q, storage, logger) {               return {                    signIn: function (username, password) {                        var deferred = $q.defer();                        if(username && password && _supported())                            _fetchTokens($http, deferred, _authUrl, { username: username, password: password }, storage);                        else                            deferred.reject();                        return deferred.promise;                    }               }          }      } } 

As you can see it's a very long code and it's even incomplete, this is only the authenticating function.

_fetchTokens makes a POST request to my web service's OAuth endpoint and retrieves the tokens form the response. It then proceeds to persist them (For example, localStorage) so they can be used for later requests and continue to live on even if the application is closed.

signIn is the public function that the client will see from outside. It simply creates the deferred object and calls _fetchTokens.

My problem is that consequent requests should send the access token (on the Authorization header). If a request is sent with an expired token, a 401 Unauthorized is returned, so this service must proceed to try to refresh the token. After successfully refreshing the token, it shall retry the original request again but with the new access token.

This means that a simple line of code from the client:

service.get('api.example.com/products'); 

Should do all this behind the scenes.

With this approach I think there's going to be a lot of replicated code, so I was looking for help on how to better structure my code. Not only for better quality but also for better readability.

I suppose such a service is not that uncommon so I hope that many people has experienced this type of situations.

     
 
 

Lista de respuestas

17
 
vote
vote
La mejor respuesta
 

Evitar el hecho de que este código parece estar fuera del tema (porque menciona que está incompleto), todavía creo que provocaría una revisión interesante.

Soy tan bonita

Primero abordemos algunas preocupaciones superficiales:

Usted tiene un uso inconsistente de los semipolones. El uso de semipolones ahora es más o menos una opción estilística debido a asi , sin embargo, si va a usar semi -colones, debe ser consistente en su solicitud y usarlos en todos los lugares relevantes, o en ninguna parte. Esto significa agregar una función semi colon después de las expresiones de la función ( DateTime.Now4 ) y después de la declaración de retorno.

SIGUIENTE UP, su convención de nombres. Si bien reconozco la convención DateTime.Now555443365 significa "variables privadas", en JavaScript, generalmente solo usamos DateTime.Now6 para todo. No hay concepto de variable privada en JavaScript, y en la fuente de la angular, las variables privadas se denotan mediante DateTime.Now7 . En última instancia, nuevamente, una decisión estilística, pero preferiría verlo solo usar DateTime.Now8 en lugar de DateTime.Now9 . En mi opinión, esto es más legible, ya que hay menos 'ruido' para que el ojo se procese.

Ahora tenemos su uso de servicios angulares. Debe probablemente siempre denota los servicios angulares con su nombre totalmente calificado, es decir, con el DateTime Now(); DateTime UtcNow(); DateTime Today(); DateTime UtcToday(); 0 . Por lo tanto, DateTime Now(); DateTime UtcNow(); DateTime Today(); DateTime UtcToday(); 1 debe ser DateTime Now(); DateTime UtcNow(); DateTime Today(); DateTime UtcToday(); 2 . Esto se debe a que me hace más fácil reconocer de inmediato "AHA, esto definitivamente será el servicio 998877665555443373

Acceso Tokens

Estoy ... inseguro de cómo usa su token de acceso. Pareces almacenarlo en localStorage. Sin embargo, esto es ideal para una sesión de una sesión para un cliente, sin embargo, no veo que realmente agregue su token de acceso a ninguna solicitud. Además, su pregunta le pregunta cómo usaría su token de acceso. Su respuesta se encuentra en el uso de un token de autorización

DateTime Now(); DateTime UtcNow(); DateTime Today(); DateTime UtcToday(); 4444433744, aunque esto se desmorona cuando usa múltiples API de diferentes dominios. Si lo está haciendo, deberá usar un interceptor HTTP (que le dará más control). Si no está haciendo eso, entonces la siguiente línea debe hacer lo que desea:

  DateTime Now(); DateTime UtcNow(); DateTime Today(); DateTime UtcToday(); 5  

Tenga cuidado con que esto pueda cambiarse en cualquier punto de cualquier otro punto de la solicitud. Si esto te hace inquieto, otra vez, use un interceptor.

Pequeños secretos sucios

esta línea:

  DateTime Now(); DateTime UtcNow(); DateTime Today(); DateTime UtcToday(); 6  

por favor, no. Esto es realmente malo. Los datos que publica en esta llamada se transformarán en cadenas de consulta. No estoy seguro de por qué hizo esto originalmente , porque este es un peligro de seguridad. Al colocar las credenciales en la cadena de consulta, el nombre de usuario y la contraseña serán visibles para cualquier persona que pueda ver la URL, que es el cliente, cualquiera que esté de pie sobre su hombro, cualquiera que mire a través de la historia de su navegador, Cualquiera en Su conexión (incluso si está utilizando HTTPS) , y su servidor registra. Usted no desea esta información en ninguna de esas ubicaciones .

En su lugar, mantenga las credenciales de nombre de usuario y contraseña dentro del cuerpo HTTP, donde pertenecen. Allí se encriptarán utilizando HTTPS (está utilizando HTTPS, ¿verdad?). En cualquier caso, para lograr una funcionalidad similar, siempre que esté utilizando una versión más reciente de angular (creo que es 1.4+), puede usar $ httpparamserializer que le evitará que tenga que hacer ese desagradable para el bucle y DateTime Now(); DateTime UtcNow(); DateTime Today(); DateTime UtcToday(); 7 tiene.

En serio, no ponga nombre de usuario y contraseñas en la URL. Los tokens de acceso JWT están bien porque vencen (y puede implementar otras medidas con ellos), pero no puede hacerlo con un nombre de usuario y una contraseña.

Anotación manual

Si está usando un paso de construcción (¿por qué no lo es?) Puede usarla ng-anotate , Lo que le impedirá tener que separar a sus dependencias 2 veces. Especificar sus dependencias 2 veces es un caso grave de duplicación de código que puede ascender a grandes dolores de cabeza para mantener la capacidad de mantenimiento (confía en mí, lo sé, en mi empresa actual no estamos usando un proceso de compilación y tenemos que especificar nuestras dependencias en 4 lugares) .

Expresiones de función VS Declaración de función.

Una expresión de función se ve así:

  DateTime Now(); DateTime UtcNow(); DateTime Today(); DateTime UtcToday(); 8  

Una declaración de función se ve así:

  DateTime Now(); DateTime UtcNow(); DateTime Today(); DateTime UtcToday(); 9  

en mi opi ion Siempre debe usar el segundo enfoque cuando sea posible, esto le dará un nombre en su función y hará que sus actividades de apagado sean significativamente más fáciles de leer. Alternativamente, puede usar un enfoque híbrido.

  void TimeTravel(TimeSpan adjustment); void TimeTravelTo(DateTime newDateTime); void FreezeTime(); void UnfreezeTime(); void RevertAllTimeTravel(); bool IsCurrentlyTimeTraveling(); 0  

Esto te da lo mejor de ambos mundo. La razón principal para tener el nombre de la función es simplemente que tiene una traza mejor de pila (se muestran los nombres de la función en los trazas de pila). Si no tuvo esto aquí, simplemente vería "Función anónima", que ayuda a nadie y tendrá que ingresar al código fuente para ver realmente el problema.

_supported

Esta función no tiene sentido, ya sea que el servicio está compatible con , o no lo es. En cualquier caso, si el servicio no es compatible, no debe incluirse en su aplicación, colóquelo en un módulo separado y no la incluya en ese caso. De cualquier manera, esta función siempre volverá a ser verdadera en su implementación actual y, por lo tanto, siento que es redundante. Lo mismo ocurre con void TimeTravel(TimeSpan adjustment); void TimeTravelTo(DateTime newDateTime); void FreezeTime(); void UnfreezeTime(); void RevertAllTimeTravel(); bool IsCurrentlyTimeTraveling(); 1 y void TimeTravel(TimeSpan adjustment); void TimeTravelTo(DateTime newDateTime); void FreezeTime(); void UnfreezeTime(); void RevertAllTimeTravel(); bool IsCurrentlyTimeTraveling(); 2 ; Ninguno de estos tiene ningún sentido. Además, es probable que usted sea que vaya a querer exponer el poder para activar y desactivar la autenticación, ya que esto causaría un lío en su aplicación, cualquiera podría llamar a esta función y modificar el estado para causar problemas . Sería mejor si lo mantiene tan inmutable posible y simplemente siempre lo tiene habilitado. Depende de su servidor, ya sea que responda o no al encabezado de autenticación.

_SessionLife

Esto tampoco tiene ningún sentido; El servidor debe determinar cuánto tiempo debe ser la sesión y debe informar al cliente cuando la sesión ha finalizado a través de una respuesta 403 en una solicitud HTTP autenticada. Tener al cliente saber cuánto tiempo debe ser una sesión, a menos que tenga sesiones de longitud fija, no tiene ningún sentido de una separación de las preocupaciones POV.

Los tokens de sesión son objetos opacos

Los tokens de sesión deben ser un objeto opaco. Como mencioné en void TimeTravel(TimeSpan adjustment); void TimeTravelTo(DateTime newDateTime); void FreezeTime(); void UnfreezeTime(); void RevertAllTimeTravel(); bool IsCurrentlyTimeTraveling(); 3, el servidor debe estar determinando la vida útil de la sesión, ya que la vida útil de la sesión no es una cosa determinista: la vida útil de la sesión puede los últimos 15 minutos normalmente, pero toda la idea de usar tokens es que usted está apaterial en el cliente, pero también puede revocar sesiones en cualquier momento. Tener una longitud de sesión aquí no tiene sentido, solo haga que el cliente responda cuando una sesión ha expirado de una respuesta HTTP no autenticada.

Encadenamiento de promesa

No necesita pasar una promesa en cada función: en su lugar, puede encadenar las promesas. Eso es difícil de explicar, pero esencialmente el resultado devuelto de una función 99887766655443384 se transferirá al siguiente void TimeTravel(TimeSpan adjustment); void TimeTravelTo(DateTime newDateTime); void FreezeTime(); void UnfreezeTime(); void RevertAllTimeTravel(); bool IsCurrentlyTimeTraveling(); 5 en la cadena: tendrá que ver mi código para ver cómo que funciona

Ponerlo todo junto

Después de todos estos puntos, aquí está su código modificado usando mi guía. Tenga en cuenta que lo que he dicho no es una guía definitiva sobre nada y no soy una autoridad en Angularjs; Todo aquí (a excepción de su punto de su consulta) es subjetivo. Tenga en cuenta que he realizado una eliminación bastante primitiva de código muerto en su código (como la eliminación del token de actualización y el tipo de token en LocalStorage). De lo que puedo ver, ninguno de los de ellos se usa. Si he eliminado algo importante, por favor, dígame para qué se usa y lo agregaré, sin embargo, esta es la razón por la que siempre debe publicar el contexto completo; -)

También he eliminado sus colonos / consignos porque no siento que sean muy útiles, francamente. Inyectar esas configuraciones utilizando la inyección de dependencia de Angular. Ya hemos establecido que void TimeTravel(TimeSpan adjustment); void TimeTravelTo(DateTime newDateTime); void FreezeTime(); void UnfreezeTime(); void RevertAllTimeTravel(); bool IsCurrentlyTimeTraveling(); 6 y void TimeTravel(TimeSpan adjustment); void TimeTravelTo(DateTime newDateTime); void FreezeTime(); void UnfreezeTime(); void RevertAllTimeTravel(); bool IsCurrentlyTimeTraveling(); 7 No tiene sentido; void TimeTravel(TimeSpan adjustment); void TimeTravelTo(DateTime newDateTime); void FreezeTime(); void UnfreezeTime(); void RevertAllTimeTravel(); bool IsCurrentlyTimeTraveling(); 8 y void TimeTravel(TimeSpan adjustment); void TimeTravelTo(DateTime newDateTime); void FreezeTime(); void UnfreezeTime(); void RevertAllTimeTravel(); bool IsCurrentlyTimeTraveling(); 9 no son valores exactamente configurables, en mi opinión. Hazles constantes en tu módulo angular (si los usa en absoluto).

Notará algunos TimeMachine0 . Esta es una señal para ngannotate para anotar esa función. Si no está usando Ngannotate, deberá reemplazarlos con una propiedad 998877666554433911 en esa instancia de función (o con la notación de la matriz estándar).

  TimeMachine2  

 

Avoiding the fact this code appears to be off-topic (because you mention it's incomplete), I still think it would prompt an interesting review.

I'm so pretty

Let's first address some superficial concerns:

You have an inconsistent use of semi-colons. Using semi-colons now is more or less a stylistic choice due to ASI, however if you're going to use semi-colons, you should be consistent in your application and use them in all relevant places, or nowhere at all. This means adding a semi colon after function expressions (var foo = function() {};) and after return statements.

Next up, your naming convention. Whilst I do recognise the _leadingUnderscore convention to mean "private variables", in JavaScript we generally just use camelCase for everything. There is no concept of private variable in JavaScript, and in Angular's source, private variables are denoted by $$variableName. Ultimately again a stylistic choice, but I would prefer to see you just use fetchTokens rather than _fetchTokens. In my opinion, this is more readable as there is less 'noise' for the eye to process.

Now we have your usage of Angular services. You should probably always denote Angular services with their fully qualified name, that is, with the $leadingDollarSign. So http should be $http. This is because it makes it easier for me to immediately recognise "aha, this will definitely be Angular's $http service" instead of there being some ambiguity.

Access tokens

I am... unsure of how you use your access token. You appear to store it in localStorage. This is great for de/re-hydration of a session for a client, however, I don't see you actually appending your access token to any requests.. in addition, your question asks how you would use your access token. Your answer lies in the use of a $http.defaults authorization token, although this does fall apart when you use multiple APIs from different domains. If you are doing so, you will have to use a http interceptor (which will give you more control). If you're not doing that, then the following line should do what you want:

$http.defaults.headers.common.Authorization = 'Bearer ' + accessToken; 

Be wary that this can be changed at any point from any other point in the application. If this makes you uneasy, again, use an interceptor.

Dirty little secretses

This line:

str.push(encodeURIComponent(p) + '=' + encodeURIComponent(data[p])); 

Please, no. This is really bad. The data you post in this call will be transformed into query strings. I'm not sure why you did this originally, because this is a security hazard. By putting the credentials into the query string, the username and password will be visible to anyone who can actually see the URL - which is the client, anyone standing over your shoulder, anyone who looks through your browser history, anyone snooping on his connection (even if he is using https), and your server logs. You do not want this information in any of those locations.

Instead, keep the username and password credentials inside of the http body, where they belong. There they will be encrypted using https (you are using https, right?). In any case, to achieve a similar functionality, providing you're using a more recent version of Angular (I believe it is 1.4+), you can use $httpParamSerializer which will avoid you having to do that nasty for loop and encodeURIComponent you have.

Seriously though, don't put username and passwords in the url. JWT access tokens are okay because those expire (and you can implement other measures with them), but you can't do that with a username and password.

Manual annotation

If you're using a build step (why aren't you?) you can use ng-annotate, which wil prevent you from having to sepcify your dependencies 2 times. Specifying your dependencies 2 times is a serious case of code duplication that can amount to large headaches for maintainability (trust me, I know, in my current company we AREN'T using a build process and we have to specify our dependencies in 4 places).

Function expressions vs Function declaration.

A function expression looks like this:

var foo = function() {} 

A function declaration looks like this:

function foo() {} 

In my opinion you should always use the second approach where possible - this will give you a name on your function and make your stacktraces significantly easier to read. Alternatively, you can use a hybrid approach..

var foo = function foo() {} 

This gives you the best of both world. The main reason to have the function name is simply that then you have a better stack trace (function names are displayed in stack traces). If you didn't have this here, you would simply see "anonymous function" - which helps no-one and you will have to step into the source code to actually see the problem.

_supported

This function doesn't make sense - either the service is supported, or it isn't. In any case, if the service isn't supported, then it should not be included in your application - put it in a separate module and don't include it in that case. Either way, this function will always return true in it's current implementation and so I feel that it is redundant. The same goes for _enabled and enabled(); neither of these make any sense. In addition, you probably aren't going to want to expose the power to switch authentication on and off as this would cause a mess in your app - anyone could call this function and modify the state to cause issues. It would be better if you keep this as immutable as possible and simply always have it enabled. It's up to your server whether or not to respond to the Authentication header.

_sessionLife

This doesn't make any sense either; the server should determine how long the session should be and should inform the client of when the session has ended via a 403 response on an authenticated HTTP request. Having the client know about how long a session should be - unless you have fixed-length sessions - doesn't make any sense from a separation of concerns POV.

Session tokens are opaque objects

Session tokens should be an opaque object. As I mentioned in _sessionLife, the server should be determining the session life as session life is not a deterministic thing - the session life may last 15 minutes normally, but the whole idea of using tokens is that you're stateless on the client, but also you can revoke sessions at any time. Having a session length here doesn't really make sense - just have the client respond to when a session has expired from an Unauthenticated HTTP response.

Promise chaining

You don't need to pass a promise into each function - you can instead chain promises. That's hard to explain but essentially the returned result from one then function will be passed on to the next then in the chain - you'll have to see my code to see how that works.

Putting it all together

After all of these points, here is your amended code using my guidance. Bear in mind that what I have said is not a definitive guide on anything and I am not an authority on AngularJS; everything here (except for your query string thing) is subjective. Note that I have done some rather primitive dead-code elimination on your code (such as removing the refresh token and token type in localStorage). From what I can see, neither of those are used. If I have removed anything important, please tell me what it is used for and I'll add it back, however this is why you should always post the full context ;-)

I've also removed your setters/getters because I don't feel that they are very useful, frankly. Inject those settings using Angular's dependency injection. We've already established that sessionLife and enabled make no sense; client and authorizationEndpoint are not exactly configurable values, in my opinion. Make them constants in your angular module (if you use them at all).

You'll notice a few // @ngInject. This is a signal to ngAnnotate to annotate that function. If you're not using ngAnnotate, you'll need to replace these with an $inject property on that function instance (or with the standard array notation).

function restProvider() {   var authUrl = '';   var clientId = '';    // You have no need for two separate functions for signing in.   // @ngInject   function signIn($http, url, username, password) {     var credentials = {       username: username,        password: password,        client_id: clientId,       grant_type: 'password'     };      return $http.post(url, credentials)       .then(function(response) {         var payload = response.data;         var accessToken = payload.access_token;         // When you refresh another token this will just be overwritten.         $http.defaults.headers.common.Authorization = 'Bearer ' + accessToken;       });   }    return {     // @ngInject     $get: function($http) {       return {         // This partially applies signIn with $http and authUrl. The next invocation will only take         // username and password as parameters. "this" is set to null.         signIn: signIn.bind(null, $http, authUrl)       };     }   }; } 
 
 
         
         
7
 
vote

creo que estas son diferentes capas de responsabilidad .

El service.get('api.example.com/products'); se llama en el controlador a la vista correspondiente. Lo que sugiero es lo siguiente:

Tiene una carpeta 99887766555443331 , donde se coloquen todos sus servicios y funciones compartidas.

Dentro de esto, crea los siguientes servicios:

  • api.js // Para manejar todas sus llamadas Crud al backend
  • authentication-service.js3 // con inicio de sesión (), cierre de sesión (), isAutenticated (), registro (), getToken (), etc.
  • authentication-session.js // Para almacenar, obtener y eliminar los tokens

Por lo tanto, puede:

  • Cambie la URL para las API sin tocar ningún otro código.
  • cambia incluso entre cookies, almacenamiento de navegación u otras soluciones de almacenamiento de token sin interferir otras partes de su aplicación

En el authentication-service.js , también tiene una función como esta para enviar su token en cada solicitud al backend:

    authService.setAuthenticationHeader = function() {     $http.defaults.headers.common['x-access-token'] = AuthenticationSession.token;   };   

Además, ya que está utilizando AngularJS, juegue con los interceptores:

  angular   .module( 'interceptors', [])   .factory('HttpInterceptors', HttpInterceptors);  HttpInterceptors.$inject = ['$q', '$injector'];  function HttpInterceptors($q, $injector) {   return {     // On request success     request: function (config) {       // console.log(config); // Contains the data about the request before it is sent.        // Return the config or wrap it in a promise if blank.       return config || $q.when(config);     },      // On request failure     requestError: function (rejection) {       //console.log('rejection', rejection); // Contains the data about the error on the request.        // Return the promise rejection.       return $q.reject(rejection);     },      // On response success     response: function (response) {       // console.log(response); // Contains the data from the response.        // Return the response or promise.       return response || $q.when(response);     },      // On response failture     responseError: function (rejection) {       if(rejection.status === 401 || rejection.status === 400) {         var state = $injector.get('$state');         state.go('login');       }        // Return the promise rejection.       return $q.reject(rejection);     }   }; }   

para que pueda manejar cada código de retorno de cada llamada HTTP en todo tu aplicación de un lugar.

Estructura de carpeta:

  • /app/common/api/api.js
  • /app/common/authentication/authentication-service.js
  • /app/common/authentication/authentication-session.js
  • /app/common/intereptors/http-interceptor.js

Incluso puede pensar en crear un 9988776665544338 como su modelo, y dentro de allí, llame al 9988776665544339 para obtener nuevos productos. Luego, dentro del controlador, solo dice common0 o algo.

Tengo una Ejemplo de repositorio que creé para seguir algunas reglas y principios. Puedes navegar a través, creo que es principalmente el mismo procedimiento cada vez.

 

I think these are different layers of responsibility.

The service.get('api.example.com/products'); is called in the controller to the corresponding view. What I suggest is the following:

You have a common folder, where all your shared services and functions are placed.

Inside this, you create following services:

  • api.js // to handle all your CRUD calls to the backend
  • authentication-service.js // with login(), logout(), isAuthenticated(), register(), getToken() etc.
  • authentication-session.js // for storing, getting, and deleting the tokens

Therefore, you can:

  • change the URL for the APIs without touching any other code.
  • switch even between cookie, browserStorage or other token-storage solutions without interfering other parts of your app

In the authentication-service.js, you also have a function like this to send your token on every request to the backend:

  authService.setAuthenticationHeader = function() {     $http.defaults.headers.common['x-access-token'] = AuthenticationSession.token;   }; 

Furthermore, since you are using AngularJS, play around with interceptors:

angular   .module( 'interceptors', [])   .factory('HttpInterceptors', HttpInterceptors);  HttpInterceptors.$inject = ['$q', '$injector'];  function HttpInterceptors($q, $injector) {   return {     // On request success     request: function (config) {       // console.log(config); // Contains the data about the request before it is sent.        // Return the config or wrap it in a promise if blank.       return config || $q.when(config);     },      // On request failure     requestError: function (rejection) {       //console.log('rejection', rejection); // Contains the data about the error on the request.        // Return the promise rejection.       return $q.reject(rejection);     },      // On response success     response: function (response) {       // console.log(response); // Contains the data from the response.        // Return the response or promise.       return response || $q.when(response);     },      // On response failture     responseError: function (rejection) {       if(rejection.status === 401 || rejection.status === 400) {         var state = $injector.get('$state');         state.go('login');       }        // Return the promise rejection.       return $q.reject(rejection);     }   }; } 

So you can handle every return code from every HTTP call throughout your app from one place.

Folder structure:

  • /app/common/api/api.js
  • /app/common/authentication/authentication-service.js
  • /app/common/authentication/authentication-session.js
  • /app/common/interceptors/http-interceptor.js

You can even think of creating a product-service.js as your model, and inside there, you call the api.js to get new products. Then, inside the controller, you just say products.getLatest() or something.

I have an example repository which I created to follow some rules and principles. You can browse through, I think it's mainly the same procedure every time.

 
 
   
   

Relacionados problema

5  Juego de Trivia de AngularJS - Controlador de separación de la Directiva  ( Angularjs trivia game separating controller from directive ) 
Estoy creando un juego de trivia (propósitos puramente para aprender) usando angularjs , nodejs y mongodb. Mi preocupación está relacionada con la forma en ...

2  Un puerto sucio de una búsqueda de imágenes de imágenes de Google Clon Jquery Plugin a Angular JS  ( A dirty port of a google image search layout clone jquery plugin to angular js ) 
Tomé la manipulación de diseño básico de una complemento jquery llamado flexibilidad . La idea es usar la etiqueta de la directiva y encapsular una pieza de ...

2  Controller.js de vehículos crud  ( Controller js of crud vehicles ) 
Nunca tomé una solicitud en una arquitectura de controlador modelo y tenemos algunas dificultades en la parte frontal. El siguiente código funciona bien, pero...

4  Estructura de archivo modular angular  ( Angular modular file structure ) 
Mi intención es separar los componentes en una base de archivos. Por ejemplo, quiero que un controlador específico tenga su propio archivo (lo mismo ocurre co...

3  Forma aceptable de usar métodos jQuery en directivas angulares  ( Acceptable way of using jquery methods in angular directives ) 
aquí es mi ejemplo de plunker de lo que estoy haciendo. jQuery es una excelente manera de usar transiciones de diapositivas y parece funcionar bien con an...

22  Inicializador Simple Async Google Maps con angularjs  ( Simple async google maps initializer with angularjs ) 
Aquí está un simple reutilizable 9988776655544337 factory Me acompañé para inicializar los mapas de Google de forma asíncrona, lo que de alguna manera no ...

1  Devolviendo un objeto del servicio  ( Returning an object from service ) 
En términos de legibilidad, eficiencia y la mejor convención, ¿cuál de los dos es mejor? SNIPPET A: angular.module("main.loadbalancer").factory("Accoun...

8  Manejo del estado compartido entre muchos elementos en angular  ( Handling shared state among a lot of elements in angular ) 
Estoy trabajando en un proyecto en angular donde tengo un número de objetos de datos similares. Cuando hace clic en alguien de ellos, su estado y la cantidad ...

2  Manejo rechazado Respuestas HTTP con estado 401  ( Handling rejected http responses with status 401 ) 
Soy un poco nuevo para JavaScript y tengo preguntas al respecto. Ahora mi proyecto práctico se está creciendo un poco más grande. Obtuve lo siguiente si el má...

7  En AngularJS, crea muchas directivas o use NG-Controller?  ( In angularjs create lots of directives or use ng controller ) 
Esta es una pregunta sobre si mi estilo de codificación sigue las mejores prácticas de AngularJS. Uno de los puntos de venta a la angular son las directivas...




© 2022 respuesta.top Reservados todos los derechos. Centro de preguntas y respuestas reservados todos los derechos