Mon Rails utilise Devise pour l'authentification. Elle a une application iOS sœur, et les utilisateurs peuvent se connecter à l'application iOS en utilisant les mêmes informations d'identification qu'ils utilisent pour l'application Web. J'ai donc besoin d'une sorte de l'API pour l'authentification.
Beaucoup de questions similaires sur ici pointent vers ce tutoriel , mais il semble être obsolète, car le module token_authenticatable
A depuis été supprimé de Devise et certaines des lignes renvoient les erreurs. (J'utilise Devise 3.2.2.) J'ai essayé de lancer le mien sur la base de ce tutoriel (et celui-ci ), mais je ne suis pas à 100% confiant - j'ai l'impression que il y a peut-être quelque chose que j'ai mal compris ou manqué.
Tout d'abord, suivant les conseils de this Gist , j'ai ajouté un attribut de texte authentication_token
À ma table users
, et le suivant à user.rb
:
before_save :ensure_authentication_token
def ensure_authentication_token
if authentication_token.blank?
self.authentication_token = generate_authentication_token
end
end
private
def generate_authentication_token
loop do
token = Devise.friendly_token
break token unless User.find_by(authentication_token: token)
end
end
Ensuite, j'ai les contrôleurs suivants:
api_controller.rb
class ApiController < ApplicationController
respond_to :json
skip_before_filter :authenticate_user!
protected
def user_params
params[:user].permit(:email, :password, :password_confirmation)
end
end
(Notez que mon application_controller
A la ligne before_filter :authenticate_user!
.)
api/sessions_controller.rb
class Api::SessionsController < Devise::RegistrationsController
prepend_before_filter :require_no_authentication, :only => [:create ]
before_filter :ensure_params_exist
respond_to :json
skip_before_filter :verify_authenticity_token
def create
build_resource
resource = User.find_for_database_authentication(
email: params[:user][:email]
)
return invalid_login_attempt unless resource
if resource.valid_password?(params[:user][:password])
sign_in("user", resource)
render json: {
success: true,
auth_token: resource.authentication_token,
email: resource.email
}
return
end
invalid_login_attempt
end
def destroy
sign_out(resource_name)
end
protected
def ensure_params_exist
return unless params[:user].blank?
render json: {
success: false,
message: "missing user parameter"
}, status: 422
end
def invalid_login_attempt
warden.custom_failure!
render json: {
success: false,
message: "Error with your login or password"
}, status: 401
end
end
api/registrations_controller.rb
class Api::RegistrationsController < ApiController
skip_before_filter :verify_authenticity_token
def create
user = User.new(user_params)
if user.save
render(
json: Jbuilder.encode do |j|
j.success true
j.email user.email
j.auth_token user.authentication_token
end,
status: 201
)
return
else
warden.custom_failure!
render json: user.errors, status: 422
end
end
end
Et dans config/routes.rb :
namespace :api, defaults: { format: "json" } do
devise_for :users
end
Je suis un peu hors de ma profondeur et je suis sûr qu'il y a quelque chose ici que mon futur moi regardera en arrière et grincera des dents (il y en a généralement). Quelques parties douteuses:
Premièrement , vous remarquerez que Api::SessionsController
Hérite de Devise::RegistrationsController
Tandis que Api::RegistrationsController
Hérite de ApiController
(J'ai également d'autres contrôleurs tels que Api::EventsController < ApiController
qui traitent des choses plus standard REST pour mes autres modèles et n'ont pas beaucoup de contacts avec Devise.) C'est un arrangement assez laid, mais je n'ai pas pu trouver un autre moyen d'accéder aux méthodes dont j'ai besoin dans Api::RegistrationsController
. Le tutoriel que j'ai lié ci-dessus a la ligne include Devise::Controllers::InternalHelpers
, Mais ce module semble avoir été supprimé dans les versions plus récentes de Devise.
Deuxièmement , j'ai désactivé la protection CSRF avec la ligne skip_before_filter :verify_authentication_token
. J'ai des doutes quant à savoir si c'est une bonne idée - je vois beaucoup de conseils conflictuels ou difficiles à comprendre sur la vulnérabilité des API JSON aux attaques CSRF - mais en ajoutant que la ligne était le seul moyen de faire fonctionner cette fichue chose.
Troisièmement , je veux m'assurer de comprendre le fonctionnement de l'authentification une fois qu'un utilisateur s'est connecté. Disons que j'ai un appel API GET /api/friends
Qui renvoie une liste des amis de l'utilisateur actuel. Si je comprends bien, l'application iOS devrait obtenir le authentication_token
De l'utilisateur dans la base de données (qui est une valeur fixe pour chaque utilisateur qui ne change jamais ??), puis la soumettre en tant que paramètre avec chaque demande, par exemple GET /api/friends?authentication_token=abcdefgh1234
, Alors mon Api::FriendsController
Pourrait faire quelque chose comme User.find_by(authentication_token: params[:authentication_token])
pour obtenir l'utilisateur actuel. Est-ce vraiment aussi simple que ça, ou est-ce que je manque quelque chose?
Donc, pour tous ceux qui ont réussi à lire jusqu'à la fin de cette question gigantesque, merci pour votre temps! Pour résumer:
Devise::RegistrationsController
Et le d'autres de ApiController
.Merci!
Vous ne voulez pas désactiver CSRF, j'ai lu que les gens pensent que cela ne s'applique pas aux API JSON pour une raison quelconque, mais c'est un malentendu. Pour le garder activé, vous souhaitez apporter quelques modifications:
côté serveur, ajoutez un after_filter à votre contrôleur de sessions:
after_filter :set_csrf_header, only: [:new, :create]
protected
def set_csrf_header
response.headers['X-CSRF-Token'] = form_authenticity_token
end
Cela générera un jeton, le placera dans votre session et le copiera dans l'en-tête de réponse pour les actions sélectionnées.
côté client (iOS), vous devez vous assurer que deux choses sont en place.
votre client doit analyser toutes les réponses du serveur pour cet en-tête et le conserver lorsqu'il est transmis.
... get ahold of response object
// response may be a NSURLResponse object, so convert:
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
// grab token if present, make sure you have a config object to store it in
NSString *token = [[httpResponse allHeaderFields] objectForKey:@"X-CSRF-Token"];
if (token)
[yourConfig setCsrfToken:token];
enfin, votre client doit ajouter ce jeton à toutes les demandes "non GET" qu'il envoie:
... get ahold of your request object
if (yourConfig.csrfToken && ![request.httpMethod isEqualToString:@"GET"])
[request setValue:yourConfig.csrfToken forHTTPHeaderField:@"X-CSRF-Token"];
La dernière pièce du puzzle est de comprendre que lors de la connexion pour concevoir, deux jetons sessions/csrf suivants sont utilisés. Un flux de connexion ressemblerait à ceci:
GET /users/sign_in ->
// new action is called, initial token is set
// now send login form on callback:
POST /users/sign_in <username, password> ->
// create action called, token is reset
// when login is successful, session and token are replaced
// and you can send authenticated requests
Votre exemple semble imiter le code du blog Devise - https://Gist.github.com/josevalim/fb706b1e933ef01e4fb6
Comme mentionné dans ce post, vous le faites de manière similaire à l'option 1, qui, selon eux, est l'option non sécurisée. Je pense que la clé est que vous ne voulez pas simplement réinitialiser le jeton d'authentification chaque fois que l'utilisateur est enregistré. Je pense que le jeton devrait être créé explicitement (par une sorte de TokenController dans l'API) et devrait expirer périodiquement.
Vous remarquerez que je dis "je pense" car (pour autant que je sache), personne n'a plus d'informations à ce sujet.
Les 10 vulnérabilités les plus courantes dans les applications Web sont documentées dans le OWASP Top 1 . Cette question mentionnait que la protection contre la contrefaçon de requêtes intersites (CSRF) était désactivée et CSRF figure dans le Top 10 OWASDP . En bref, CSRF est utilisé par les attaquants pour effectuer des actions en tant qu'utilisateur authentifié. La désactivation de la protection CSRF entraînera des vulnérabilités à haut risque dans une application et sapera l'objectif d'avoir un système d'authentification sécurisé. Il est probable que la protection CSRF échoue, car le client ne parvient pas à transmettre le jeton de synchronisation CSRF.
Lisez l'intégralité du top 10 de l'OWASP, à défaut de le faire extrêmement dangereux . Portez une attention particulière à Authentification brisée et gestion de session , consultez également le Aide-mémoire de gestion de session .