Redefinição de senha RESTful

107

Qual é a maneira adequada de estruturar um recurso RESTful para redefinir uma senha?

Este recurso deve ser uma redefinição de senha para alguém que perdeu ou esqueceu sua senha. Isso invalida a senha antiga e envia uma senha por e-mail.

As duas opções que tenho são:

POST /reset_password/{user_name}

ou...

POST /reset_password
   -Username passed through request body

Tenho certeza de que a solicitação deve ser um POST. Estou menos confiante de que selecionei um nome apropriado. E não tenho certeza se o user_name deve ser passado pelo URL ou corpo da solicitação.

Chris Dutrow
fonte

Respostas:

54

ATUALIZAÇÃO: (mais para comentar abaixo)

Eu iria para algo assim:

POST /users/:user_id/reset_password

Você tem uma coleção de usuários, onde o único usuário é especificado pelo {user_name}. Você então especificaria a ação na qual operar, que neste caso é reset_password. É como dizer "Criar ( POST) uma nova reset_passwordação para {user_name}".


Resposta anterior:

Eu iria para algo assim:

PUT /users/:user_id/attributes/password
    -- The "current password" and the "new password" passed through the body

Você teria duas coleções, uma coleção de usuários e uma coleção de atributos para cada usuário. O usuário é especificado pelo :user_ide o atributo é especificado por password. A PUToperação atualiza o membro endereçado da coleção.

Daniel Vassallo
fonte
10
Eu concordo com sua solução atualizada (POST). As solicitações PUT devem ser idempotentes (ou seja, as solicitações repetidas não devem afetar o resultado). Este não é o caso com solicitações POST.
Ross McFarlane
16
Eu mudaria reset_password para password_reset
Richard Knop
9
Espere, pessoal ... isso essencialmente não permitiria que QUALQUER UM redefinisse a senha de alguém? Pois, se for para alguém que esqueceu a senha atual, o usuário afetado não pode ser autenticado com a senha atual. Basicamente, isso significa que essa API não pode aceitar nenhuma senha - permitindo, assim, que qualquer pessoa redefina a senha de alguém e, se a API a retornar, até mesmo obter a senha de qualquer usuário conhecido ?? Ou estou perdendo alguma coisa
transient_loop
39
O problema com / user / {id} / senha e similares é que você pode não saber o "id" do usuário. Você saberia seu "nome de usuário" ou "e-mail" ou "telefone", mas não o "id".
coolaj86,
17
A falha fundamental dessa abordagem é que ela pressupõe que você já conhece o ID do usuário. Isso será verdade em algumas circunstâncias, mas como fazer isso quando o nome de usuário ou ID de usuário não for conhecido se o usuário precisar apenas especificar um e-mail para a redefinição.
Alappin
94

Usuários não autenticados

Fazemos uma PUTsolicitação em um api/v1/account/passwordendpoint e exigimos um parâmetro com o e-mail da conta correspondente para identificar a conta para a qual o usuário deseja redefinir (atualizar) a senha:

PUT : /api/v1/account/password?email={[email protected]}

Nota: Como @DougDomeny mencionou em seu comentário, passar o e-mail como uma string de consulta no url é um risco de segurança. Os parâmetros GET não são expostos durante o uso https(e você deve sempre usar uma httpsconexão adequada para essas solicitações), mas há outros riscos de segurança envolvidos. Você pode ler mais sobre este tópico nesta postagem do blog aqui .

Passar o e-mail no corpo da solicitação seria uma alternativa mais segura do que passá-lo como um parâmetro GET:

PUT : /api/v1/account/password

Corpo da solicitação:

{
    "email": "[email protected]"
}

A resposta tem um significado de resposta 202aceito :

A solicitação foi aceita para processamento, mas o processamento não foi concluído. A solicitação pode ou não ser atendida, pois pode ser proibida quando o processamento realmente ocorrer. Não há recurso para reenviar um código de status de uma operação assíncrona como esta.

O usuário receberá um e-mail em [email protected]e o processamento da solicitação de atualização depende das ações realizadas com o link do e-mail.

https://example.com/password-reset?token=1234567890

Abrir o link deste e-mail direcionará para um formulário de redefinição de senha no aplicativo front end que usa o token de redefinição de senha do link como entrada para um campo de entrada oculto (o token faz parte do link como uma string de consulta). Outro campo de entrada permite que o usuário defina uma nova senha. Uma segunda entrada para confirmar a nova senha será usada para validação no front-end (para evitar erros de digitação).

Nota: No email também poderíamos mencionar que caso o usuário não inicialize nenhuma redefinição de senha ele pode ignorar o email e continuar usando o aplicativo normalmente com sua senha atual

Quando o formulário é enviado com a nova senha e o token como entradas, o processo de redefinição da senha ocorrerá. Os dados do formulário serão enviados com uma PUTsolicitação novamente, mas desta vez incluindo o token e substituiremos a senha do recurso por um novo valor:

PUT : /api/v1/account/password

Corpo da solicitação:

{
    "token":"1234567890",
    "new":"password"
}

A resposta será uma resposta 204sem conteúdo

O servidor atendeu à solicitação, mas não precisa retornar um corpo de entidade e pode desejar retornar metainformações atualizadas. A resposta PODE incluir metainformações novas ou atualizadas na forma de cabeçalhos de entidade, que, se presentes, DEVEM ser associados à variante solicitada.

Usuários autenticados

Para usuários autenticados que desejam alterar sua senha, a PUTsolicitação pode ser realizada imediatamente sem o e-mail (a conta para a qual estamos atualizando a senha é conhecida pelo servidor). Nesse caso, o formulário apresentará dois campos:

PUT : /api/v1/account/password

Corpo da solicitação:

{
    "old":"password",
    "new":"password"
}
Wilt
fonte
Em seu primeiro parágrafo, você diz COLOCAR, mas o exemplo abaixo diz EXCLUIR. O que é preciso?
jpierson
Isso expõe o endereço de e-mail no URL, o que seria mais seguro para dados JSON.
Doug Domeny
@DougDomeny Sim, enviar o e-mail como dados json provavelmente seria melhor. Eu adicionei isso à resposta como uma opção alternativa mais segura, caso contrário, a solução pode ser a mesma.
Wilt
@Wilt: Não seria uma operação PATCH? PUT requer o envio do recurso completo
j10
1
@jitenshah Bom argumento. Ao escrever isso, pensei que PUT seria melhor, mas não me lembro exatamente por quê. Eu concordo com o seu raciocínio de que o patch pode ser mais adequado para este caso.
Wilt
18

Vamos usar o uber-RESTful por um segundo. Por que não usar a ação DELETE para a senha para acionar uma redefinição? Faz sentido, não é? Afinal, você está efetivamente descartando a senha existente em favor de outra.

Isso significa que você faria:

DELETE /users/{user_name}/password

Agora, duas grandes advertências:

  1. HTTP DELETE deve ser idempotente (uma palavra chique para dizer "não é grande coisa se você fizer isso várias vezes"). Se estiver fazendo as coisas padrão, como enviar um e-mail de "Redefinição de senha", você terá problemas. Você poderia contornar isso marcando o usuário / senha com um sinalizador booleano "Is Reset". Em cada exclusão, você verifica este sinalizador; se não estiver definido , você pode redefinir a senha e enviar seu e-mail. (Observe que esse sinalizador pode ter outros usos também.)

  2. Você não pode usar HTTP DELETE através de um formulário , então você terá que fazer uma chamada AJAX e / ou tunelar o DELETE através do POST.

Craig Walker
fonte
9
Idéia interessante. No entanto, não acho que se DELETEencaixe bem aqui. Você estaria substituindo a senha por uma gerada aleatoriamente, eu acho, então DELETEpode ser enganoso. Eu prefiro o Create (POST) new reset_password action, onde o substantivo (recurso) em que você estaria agindo é a "ação reset_password". Isso se encaixa bem para enviar e-mails também, uma vez que a ação encapsula todos esses detalhes de nível inferior. POSTnão é idempotente.
Daniel Vassallo
Eu gosto da proposta. O problema 1 poderia ser resolvido usando solicitações condicionais, ou seja, HEAD que envia ETag + DELETE e cabeçalho If-Match. Se alguém tentar excluir uma senha que não está mais ativa, receberá um 412.
whiskeysierra
1
Eu evitaria DELETE. Você está atualizando, pois a mesma entidade / conceito receberá um novo valor. Mas, na verdade, normalmente não está acontecendo agora, mas somente após o envio da nova senha em uma solicitação diferente posterior (após um e-mail de redefinição de senha) - Hoje em dia ninguém envia uma nova senha por e-mail, mas um token para redefini-la em uma nova solicitação com um determinado token, certo?
antonio.fornie
11
E se o usuário lembrar sua senha logo após fazer uma solicitação de redefinição? Que tal algum bot tentando redefinir contas aleatórias? Nesse caso, o usuário deve ter permissão para ignorar o e-mail de redefinição (o e-mail deve dizer isso), o que significa que o sistema não deve excluir ou atualizar as senhas por si só.
Maxime Laval
3
@MaximeLaval Esse é um ponto muito bom. Na verdade, você está "Criando uma solicitação de redefinição", que seria um POST.
Craig Walker
12

Freqüentemente, você não deseja excluir ou destruir a senha existente do usuário na solicitação inicial, pois isso pode ter sido acionado (não intencionalmente ou intencionalmente) por um usuário que não tem acesso ao e-mail. Em vez disso, atualize um token de redefinição de senha no registro do usuário e envie-o em um link incluído em um e-mail. Clicar no link confirma que o usuário recebeu o token e deseja atualizar sua senha. Idealmente, isso seria sensível ao tempo também.

A ação RESTful neste caso seria um POST: acionando a ação de criação no controlador PasswordResets. A própria ação atualizaria o token e enviaria um e-mail.

Mark Swardstrom
fonte
9

Na verdade, estou procurando uma resposta, sem querer fornecer uma - mas "reset_password" parece errado para mim em um contexto REST porque é um verbo, não um substantivo. Mesmo se você disser que está fazendo um substantivo de "ação de redefinição" - usando essa justificativa, todos os verbos são substantivos.

Além disso, pode não ter ocorrido a alguém que procurava a mesma resposta que você pudesse obter o nome de usuário por meio do contexto de segurança e não precisar enviá-lo por meio da url ou do corpo, o que me deixa nervoso.

orbfish
fonte
4
Talvez reset-passwordsoe como um verbo, mas você pode facilmente invertê-lo ( password-reset) para torná-lo um substantivo. E se você modelou seu aplicativo usando Event Sourcing ou mesmo se você apenas tem qualquer tipo de auditoria, faz sentido que você realmente tenha uma entidade real com este nome e pode até permitir GETs nela para usuários ou administradores verem histórico (obviamente mascarando o texto da senha). Não me deixa nem um pouco nervoso. E quanto a pegar o nome de usuário automaticamente no servidor - você pode, mas então como você lida com coisas como administração / personificação?
Aaronaught
1
Não há nada de errado em usar verbo em REST. Desde que seja utilizado nos locais apropriados. Eu acho que isso é mais um controlador do que um reforço, e reset-passwordconsigo descrever bem seus efeitos.
Anders Östman
6

Acho que a melhor ideia seria:

DELETE /api/v1/account/password    - To reset the current password (in case user forget the password)
POST   /api/v1/account/password    - To create new password (if user has reset the password)
PUT    /api/v1/account/{userId}/password    - To update the password (if user knows is old password and new password)

Em relação ao fornecimento dos dados:

  • Para redefinir a senha atual

    • o e-mail deve ser fornecido no corpo.
  • Para criar uma nova senha (após redefinir)

    • nova senha, código de ativação e emailID devem ser fornecidos no corpo.
  • Para atualizar a senha (para usuário conectado)

    • senha antiga, a nova senha deve ser mencionada no corpo.
    • UserId em Params.
    • Token de autenticação nos cabeçalhos.
codesnooker
fonte
2
Como comentado em outras respostas, "DELETE / api / v1 / account / password" é uma má ideia, pois qualquer um pode redefinir a senha de qualquer um.
maximedupre
Precisamos de um ID de e-mail registrado para redefinir a senha. As chances de saber o ID de e-mail de um usuário desconhecido são muito ruins, a menos que estejamos executando um site como o Facebook e tenhamos toneladas de IDs de e-mail coletados por qualquer meio. Então, as políticas de segurança serão definidas de acordo. Qual é a sua sugestão para redefinir a senha de alguém?
codesnooker
5

Existem algumas considerações a serem feitas:

Redefinições de senha não são idempotentes

Uma alteração de senha afeta os dados usados ​​como credenciais para executá-la, o que, como resultado, pode invalidar tentativas futuras se a solicitação for simplesmente repetida literalmente enquanto as credenciais armazenadas são alteradas. Por exemplo, se um token de reconfiguração temporário for usado para permitir a mudança, como é comum em uma situação de senha esquecida, esse token deve expirar após a mudança de senha bem-sucedida, o que novamente anula outras tentativas de replicação da solicitação. Portanto, uma abordagem RESTful para uma alteração de senha parece ser uma tarefa mais adequada do POSTque PUT.

ID ou e-mail na carga de dados é provavelmente redundante

Embora isso não seja contra REST e possa ter algum propósito especial, geralmente é desnecessário especificar um ID ou endereço de e-mail para uma redefinição de senha. Pense nisso, por que você forneceria o endereço de e-mail como parte dos dados para uma solicitação que deveria passar por autenticação de uma forma ou de outra? Se o usuário estiver simplesmente alterando sua senha, ele precisa se autenticar para fazer isso (por meio de nome de usuário: senha, e-mail: senha ou token de acesso fornecido por cabeçalhos). Portanto, temos acesso à conta deles a partir dessa etapa. Se eles tivessem esquecido a senha, eles teriam recebido um token de redefinição temporário (via e-mail) que eles poderiam usar especificamente como credenciais para realizar a alteração. E neste caso a autenticação via token deve ser suficiente para identificar sua conta.

Levando todos os itens acima em consideração, aqui está o que acredito ser o esquema adequado para uma alteração de senha RESTful:

Method: POST
url: /v1/account/password
Access Token (via headers): pwd_rst_token_b3xSw4hR8nKWE1d4iE2s7JawT8bCMsT1EvUQ94aI
data load: {"password": "This 1s My very New Passw0rd"}
Michael Ekoka
fonte
Uma declaração de que um espaço reservado requer informações fora de banda não é totalmente verdadeira. Um tipo de mídia especial pode descrever a sintaxe e a semântica de certos elementos de uma solicitação ou resposta. Portanto, é possível para um tipo de mídia definir que um URI contido em um determinado campo pode definir o espaço reservado para certos dados e a semântica define ainda que um e-mail de usuário codificado ou o que não deve ser incluído em vez do espaço reservado. Os clientes e servidores que respeitam esse tipo de mídia ainda serão compatíveis com os princípios da arquitetura RESTful.
Roman Vottner
1
Em POSTrelação ao PUT RFC 7231 especifica que uma atualização parcial pode ser alcançada por meio da sobreposição de dados de dois recursos, mas é questionável se algo como /v1/account/passwordrealmente compensa um bom recurso. Assim POSTcomo o canalha do exército suíço da Web, que pode ser usado se nenhum dos outros métodos for viável, considerar PATCHtambém pode ser uma opção para definir a nova senha.
Roman Vottner
e quanto ao URL para solicitar a redefinição da senha quando eles não sabem a senha?
Dan Carter
2

Eu não teria algo que mudasse a senha e enviasse uma nova se você decidir usar o método / users / {id} / password e se ater à sua ideia de que a solicitação é um recurso próprio. ou seja, / solicitação de senha de usuário / é o recurso, e é usado PUT, as informações do usuário devem estar no corpo. Eu não mudaria a senha, porém, vou enviar um e-mail para o usuário que contém um link para uma página que contém um request_guid, que pode ser passado junto com uma solicitação para POST / user / {id} / password /? Request_guid = xxxxx

Isso mudaria a senha e não permitiria que alguém enviasse uma mangueira a um usuário solicitando uma mudança de senha.

Além disso, o PUT inicial pode falhar se houver uma solicitação pendente.

bicicletas
fonte
0

Nós atualizamos a senha do usuário registrado PUT / v1 / users / password - identifique o ID do usuário usando AccessToken.

Não é seguro trocar o ID do usuário. A API Restful deve identificar o usuário usando AccessToken recebido no cabeçalho HTTP.

Exemplo em bota de mola

@putMapping(value="/v1/users/password")
public ResponseEntity<String> updatePassword(@RequestHeader(value="Authorization") String token){
/* find User Using token */
/* Update Password*?
/* Return 200 */
}
Dapper Dan
fonte