web-dev-qa-db-fra.com

Envoyer auth_token pour authentification à ActionCable

module ApplicationCable
  class Connection < ActionCable::Connection::Base
    identified_by :current_user

    def connect
      #puts params[:auth_token]
      self.current_user = find_verified_user
      logger.add_tags 'ActionCable', current_user.name
   end

  end
end

Je n'utilise pas Web comme point de terminaison pour le câble d'action. Je souhaite donc utiliser auth_token pour l'authentification. Par défaut, le câble d'action utilise l'ID utilisateur de la session pour l'authentification. Comment passer les paramètres à la méthode de connexion?

16
alxibra

J'ai réussi à envoyer mon jeton d'authentification en tant que paramètre de requête.

Lors de la création de mon consommateur dans mon application javascript, je passe le jeton à l'URL du serveur de câble comme suit:

wss://myapp.com/cable?token=1234

Dans ma connexion par câble, je peux obtenir cette token en accédant au request.params:

module ApplicationCable
  class Connection < ActionCable::Connection::Base
    identified_by :current_user

    def connect
      self.current_user = find_verified_user
      logger.add_tags 'ActionCable', current_user.name
    end

    protected:
    def find_verified_user
      if current_user = User.find_by(token: request.params[:token])
        current_user
      else
        reject_unauthorized_connection
      end
    end
  end
end

Ce n'est clairement pas idéal, mais je ne pense pas que vous puissiez envoyer des en-têtes personnalisés lors de la création de la prise Web.

35
Pierre Fraisse

La réponse de Pierre fonctionne. Cependant, il est judicieux d’être explicite quant à l’attente de ces paramètres dans votre application.

Par exemple, dans l’un de vos fichiers de configuration (par exemple, application.rb, development.rb, etc.), vous pouvez procéder comme suit:

config.action_cable.mount_path = '/cable/:token'

Et puis simplement y accéder depuis votre classe Connection avec:

request.params[:token]
15
Yuval Karmi

Malheureusement pour les connexions websocket, les en-têtes supplémentaires et personnalisés ne sont pas pris en charge.1 par la plupart2 clients et serveurs websocket. Les options possibles sont donc:

  • Attacher en tant que paramètre d'URL et l'analyser sur le serveur

    path.to.api/cable?token=1234
    
    # and parse it like
    request.params[:token]
    

Cons : Il peut être vulnérable car il peut se retrouver dans les journaux et les informations de processus système disponibles pour les autres utilisateurs ayant accès au serveur, plus ici

Solution : Cryptez le jeton et attachez-le. Ainsi, même s'il est visible dans les journaux, il ne servirait à rien jusqu'à ce qu'il soit déchiffré.

  • Associez JWT à l’un des paramètres autorisés. 

Côté client: 

# Append jwt to protocols
new WebSocket(url, existing_protocols.concat(jwt))

J'ai créé une bibliothèque JS action-cable-react-jwt pour React et React-Nativethat ne fait que cela. Sentez-vous libre de l'utiliser.

Du côté serveur:

# get the user by 
# self.current_user = find_verified_user

def find_verified_user
  begin
    header_array = self.request.headers[:HTTP_SEC_WEBSOCKET_PROTOCOL].split(',')
    token = header_array[header_array.length-1]
    decoded_token = JWT.decode token, Rails.application.secrets.secret_key_base, true, { :algorithm => 'HS256' }
    if (current_user = User.find((decoded_token[0])['sub']))
      current_user
    else
      reject_unauthorized_connection
    end
  rescue
    reject_unauthorized_connection
  end
end

1 La plupart des API Websocket (y compris de Mozilla ) ressemblent à celle ci-dessous:

Le constructeur WebSocket accepte un paramètre obligatoire et un paramètre facultatif :

WebSocket WebSocket(
  in DOMString url,
  in optional DOMString protocols
);

WebSocket WebSocket(
  in DOMString url,
  in optional DOMString[] protocols
);

url

L'URL à laquelle se connecter; il devrait s'agir de l'URL à laquelle le serveur WebSocket répondra.

protocolsFacultatif

Soit une chaîne de protocole unique, soit un tableau de chaînes de protocole. Ces chaînes Sont utilisées pour indiquer les sous-protocoles, de sorte qu'un serveur Puisse implémenter plusieurs sous-protocoles WebSocket (par exemple, vous pouvez Vouloir qu'un serveur puisse: gérer différents types d’interactions en fonction du protocole spécifié). Si vous ne spécifiez pas de chaîne de protocole , Une chaîne vide est utilisée.

2 Il y a toujours des exceptions, par exemple, ce noeud.js lib ws permet de créer des en-têtes personnalisés. Vous pouvez donc utiliser l'en-tête Authorization: Bearer token habituel et l'analyser sur le serveur, mais le client et le serveur doivent utiliser ws.

pour ajouter aux réponses précédentes, si vous utilisiez votre JWT en tant que paramètre, vous devrez au moins btoa(your_token) @js et Base64.decode64(request.params[:token]) @ @Rails comme Rails considère le point '.' un séparateur pour que votre jeton soit coupé @Rails params side

1
mmln

Comme je l'ai déjà indiqué dans un commentaire, la réponse acceptée est ce n'est pas une bonne idée , tout simplement parce que la convention veut que l'URL ne doit pas contient de telles données sensibles. Vous pouvez trouver plus d'informations ici: https://tools.ietf.org/html/rfc6750#section-5.3 (bien que cela concerne spécifiquement OAuth).

Il existe cependant une autre approche: Utiliser l’authentification de base HTTP via l’URL ws . J'ai constaté que la plupart des clients Websocket vous permettent de définir implicitement les en-têtes en ajoutant l'URL avec l'authentification de base http comme ceci: wss://user:[email protected]/cable.

Cela ajoutera l'en-tête Authorization avec une valeur de Basic .... Dans mon cas, j’utilisais imagine avec devise-jwt et j’ai simplement mis en œuvre une stratégie héritée de celle fournie dans la gemme, qui tire le jwt de l’en-tête Authorization. J'ai donc défini l'URL comme ceci: wss://[email protected]/cable qui définit l'en-tête sur ceci (pseudo): Basic base64("token:") et l'analyse dans la stratégie.

1
phikes

Au cas où l’un d’entre vous voudrait utiliser ActionCable.createCustomer. Mais avoir un jeton renouvelable comme je le fais:

const consumer = ActionCable.createConsumer("/cable")
const consumer_url = consumer.url
Object.defineProperty(
  consumer, 
  'url', 
  {
      get: function() { 
        const token = localStorage.getItem('auth-token')
        const email = localStorage.getItem('auth-email')
        return consumer_url+"?email="+email+"&token="+token
      }
  });
return consumer; 

Ensuite, en cas de perte de connexion, il sera ouvert avec un nouveau jeton.

1
Jan Topiński

Concernant la sécurité de Réponse de Pierre : Si vous utilisez le protocole WSS, qui utilise SSL pour le cryptage, les principes d'envoi de données sécurisées doivent être identiques à ceux du protocole HTTPS. Lorsque vous utilisez SSL, les paramètres de chaîne de requête sont cryptés, ainsi que le corps de la demande. Ainsi, si dans les API HTTP, vous envoyez tout type de jeton via HTTPS et le considérez comme sécurisé, il devrait en être de même pour WSS. Rappelez-vous que, comme pour HTTPS, n'envoyez pas d'informations d'identification telles que mot de passe via des paramètres de requête, car l'URL de la demande pourrait être enregistrée sur un serveur et donc stockée avec votre mot de passe. Utilisez plutôt des objets tels que des jetons émis par le serveur.

Vous pouvez également vérifier ceci (cela décrit essentiellement quelque chose comme l’authentification JWT + la vérification de l’adresse IP): https://devcenter.heroku.com/articles/websocket-security#authentication-authorization .

0
Przemek Piotrowski

Une autre façon (comme je l’ai fait à la fin au lieu de mon autre réponse) serait d’avoir une action authenticate sur votre canal. Je l'ai utilisé pour déterminer l'utilisateur actuel et le définir dans la connexion/le canal. Tous les éléments sont envoyés via des Websockets, donc les informations d'identification ne sont pas un problème ici lorsque nous les avons cryptées (c'est-à-dire wss).

0
phikes