Restablecimiento de contraseña RESTful

107

¿Cuál es la forma correcta de estructurar un recurso RESTful para restablecer una contraseña?

Este recurso está destinado a restablecer la contraseña para alguien que ha perdido u olvidado su contraseña. Invalida su contraseña anterior y les envía una contraseña por correo electrónico.

Las dos opciones que tengo son:

POST /reset_password/{user_name}

o...

POST /reset_password
   -Username passed through request body

Estoy bastante seguro de que la solicitud debe ser POST. Tengo menos confianza en haber seleccionado un nombre apropiado. Y no estoy seguro de si el nombre de usuario debe pasarse a través de la URL o el cuerpo de la solicitud.

Chris Dutrow
fuente

Respuestas:

54

ACTUALIZACIÓN: (además del comentario a continuación)

Yo iría por algo como esto:

POST /users/:user_id/reset_password

Tiene una colección de usuarios, donde el usuario único lo especifica {user_name}. Luego, debe especificar la acción sobre la que operar, que en este caso es reset_password. Es como decir "Crear ( POST) una nueva reset_passwordacción para {user_name}".


Respuesta anterior:

Yo iría por algo como esto:

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

Tendría dos colecciones, una colección de usuarios y una colección de atributos para cada usuario. El usuario está especificado por :user_idy el atributo está especificado por password. La PUToperación actualiza el miembro de la colección al que se dirige.

Daniel Vassallo
fuente
10
Estoy de acuerdo con su solución actualizada (POST). Las solicitudes PUT deben ser idempotentes (es decir, las solicitudes repetidas no deben afectar el resultado). Este no es el caso de las solicitudes POST.
Ross McFarlane
16
Cambiaría reset_password a password_reset
Richard Knop
9
Esperen chicos ... ¿esto no permitiría esencialmente a NADIE restablecer la contraseña de alguien? Como, si esto es para alguien que olvidó la contraseña actual, el usuario afectado no puede ser autenticado con la contraseña actual. Entonces, esencialmente, esto significa que esta API no puede aceptar ninguna contraseña, lo que permite a cualquiera restablecer la contraseña de alguien y, si la API la devuelve, ¿incluso obtener la contraseña de cualquier usuario conocido? ¿O me estoy perdiendo algo
Transient_loop
39
El problema con / user / {id} / password y similares es que es posible que no conozca la "identificación" del usuario. Sabría su "nombre de usuario", "correo electrónico" o "teléfono", pero no la "identificación".
coolaj86
17
El defecto fundamental de este enfoque es que asume que ya conoce la identificación del usuario. Esto será cierto en algunas circunstancias, pero ¿cómo se hace? Si no se conoce el nombre de usuario o la identificación de usuario, el usuario solo necesita especificar un correo electrónico para el restablecimiento.
Alappin
94

Usuarios no autenticados

Hacemos una PUTsolicitud en un api/v1/account/passwordendpoint y requerimos un parámetro con el correo electrónico de la cuenta correspondiente para identificar la cuenta para la cual el usuario desea restablecer (actualizar) la contraseña:

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

Nota: Como @DougDomeny mencionó en su comentario, pasar el correo electrónico como una cadena de consulta en la URL es un riesgo de seguridad. Los parámetros GET no se exponen al usarlos https(y siempre debe usar una httpsconexión adecuada para tales solicitudes), pero existen otros riesgos de seguridad involucrados. Puede leer más sobre este tema en esta publicación de blog aquí .

Pasar el correo electrónico en el cuerpo de la solicitud sería una alternativa más segura que pasarlo como un parámetro GET:

PUT : /api/v1/account/password

Cuerpo de la solicitud:

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

La respuesta tiene un significado de respuesta 202aceptado :

La solicitud ha sido aceptada para su procesamiento, pero el procesamiento no se ha completado. La solicitud podría o no eventualmente ser atendida, ya que podría ser rechazada cuando el procesamiento realmente tenga lugar. No existe la posibilidad de volver a enviar un código de estado desde una operación asincrónica como esta.

El usuario recibirá un correo electrónico en [email protected]y el procesamiento de la solicitud de actualización depende de las acciones tomadas con el enlace del correo electrónico.

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

Al abrir el enlace de este correo electrónico, se dirigirá a un formulario de restablecimiento de contraseña en la aplicación de interfaz que utiliza el token de restablecimiento de contraseña del enlace como entrada para un campo de entrada oculto (el token es parte del enlace como una cadena de consulta). Otro campo de entrada permite al usuario establecer una nueva contraseña. Una segunda entrada para confirmar la nueva contraseña se utilizará para la validación en el front-end (para evitar errores tipográficos).

Nota: En el correo electrónico también podríamos mencionar que en caso de que el usuario no haya inicializado ningún restablecimiento de contraseña, puede ignorar el correo electrónico y seguir usando la aplicación normalmente con su contraseña actual.

Cuando el formulario se envía con la nueva contraseña y el token como entradas, se llevará a cabo el proceso de restablecimiento de contraseña. Los datos del formulario se enviarán con una PUTsolicitud nuevamente, pero esta vez incluyendo el token y reemplazaremos la contraseña del recurso con un nuevo valor:

PUT : /api/v1/account/password

Cuerpo de la solicitud:

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

La respuesta será una respuesta 204sin contenido.

El servidor ha cumplido la solicitud, pero no necesita devolver un cuerpo de entidad y es posible que desee devolver metainformación actualizada. La respuesta PUEDE incluir metainformación nueva o actualizada en forma de encabezados de entidad, que si están presentes DEBERÍAN estar asociados con la variante solicitada.

Usuarios autenticados

Para los usuarios autenticados que desean cambiar su contraseña, la PUTsolicitud se puede realizar de inmediato sin el correo electrónico (el servidor conoce la cuenta para la que estamos actualizando la contraseña). En tal caso, el formulario enviará dos campos:

PUT : /api/v1/account/password

Cuerpo de la solicitud:

{
    "old":"password",
    "new":"password"
}
Marchitar
fuente
En su primer párrafo, dice PUT pero el siguiente ejemplo dice BORRAR. ¿Cuál es exacto?
jpierson
Esto expone la dirección de correo electrónico en la URL, lo que sería más seguro como datos JSON.
Doug Domeny
@DougDomeny Sí, enviar el correo electrónico como datos json probablemente sería mejor. Agregué esto a la respuesta como una opción alternativa más segura, de lo contrario, la solución puede ser la misma.
March
@Wilt: ¿No sería esto una operación PATCH? PUT requiere enviar el recurso completo
j10
1
@jitenshah Buen punto. Al escribir esto pensé que PUT sería mejor, pero no recuerdo exactamente por qué. Estoy de acuerdo con su razonamiento de que el parche podría ser más adecuado para este caso.
March
18

Consigamos súper RESTful por un segundo. ¿Por qué no utilizar la acción DELETE para que la contraseña active un restablecimiento? Tiene sentido, ¿no? Después de todo, efectivamente está descartando la contraseña existente en favor de otra.

Eso significa que harías:

DELETE /users/{user_name}/password

Ahora, dos grandes advertencias:

  1. Se supone que HTTP DELETE es idempotente (una palabra elegante para decir "no es gran cosa si lo haces varias veces"). Si está haciendo cosas estándar como enviar un correo electrónico de "Restablecimiento de contraseña", entonces tendrá problemas. Puede solucionar esto etiquetando el usuario / contraseña con un indicador booleano "Se restablece". En cada eliminación, marca esta bandera; Si no se establece a continuación, puede restablecer la contraseña y envía su correo electrónico. (Tenga en cuenta que tener esta bandera también puede tener otros usos).

  2. No puede usar HTTP DELETE a través de un formulario , por lo que tendrá que hacer una llamada AJAX y / o tunelizar el DELETE a través del POST.

Craig Walker
fuente
9
Idea interesante. Sin embargo, no veo que DELETEencaje bien aquí. Estaría sustituyendo la contraseña por una generada al azar, supongo, por lo que DELETEpodría ser engañoso. Prefiero el Create (POST) new reset_password action, donde el sustantivo (recurso) sobre el que actuarías es "reset_password action". Esto también se ajusta bien para enviar correos electrónicos, ya que la acción encapsula todos estos detalles de nivel inferior. POSTno es idempotente.
Daniel Vassallo
Me gusta la propuesta. El problema 1 podría resolverse mediante solicitudes condicionales, es decir, HEAD que envía ETag + DELETE y el encabezado If-Match. Si alguien intenta eliminar una contraseña que ya no está activa, obtendrá un 412.
whiskeysierra
1
Evitaría BORRAR. Estás actualizando, ya que la misma entidad / concepto obtendrá un nuevo valor. Pero en realidad, por lo general, ni siquiera está sucediendo ahora, sino solo después de enviar la nueva contraseña en una solicitud diferente posterior (después de un correo de restablecimiento de contraseña). Hoy en día, nadie envía una nueva contraseña por correo, sino un token para restablecerla en una nueva solicitud con un token dado, ¿verdad?
antonio.fornie
11
¿Qué pasa si el usuario recuerda su contraseña justo después de realizar una solicitud de restablecimiento? ¿Qué pasa con algún bot que intenta restablecer cuentas aleatorias? El usuario debe poder ignorar el correo electrónico de restablecimiento en tal caso (el correo electrónico debe decirlo), lo que significa que su sistema no debe eliminar o actualizar las contraseñas por sí mismo.
Maxime Laval
3
@MaximeLaval Ese es un muy buen punto. Realmente, estás "Creando una solicitud de reinicio", que sería una POST.
Craig Walker
12

A menudo, no desea eliminar o destruir la contraseña existente del usuario en la solicitud inicial, ya que esto puede haber sido activado (involuntaria o intencionalmente) por un usuario que no tiene acceso al correo electrónico. En su lugar, actualice un token de restablecimiento de contraseña en el registro de usuario y envíelo en un enlace incluido en un correo electrónico. Hacer clic en el enlace confirmaría que el usuario recibió el token y deseaba actualizar su contraseña. Idealmente, esto también sería sensible al tiempo.

La acción RESTful en este caso sería una POST: desencadenando la acción de creación en el controlador PasswordResets. La acción en sí misma actualizaría el token y enviaría un correo electrónico.

Mark Swardstrom
fuente
9

En realidad, estoy buscando una respuesta, no es mi intención proporcionar una, pero "reset_password" me suena mal en un contexto REST porque es un verbo, no un sustantivo. Incluso si dices que estás haciendo un sustantivo de "acción de restablecimiento", usando esta justificación, todos los verbos son sustantivos.

Además, es posible que a alguien que busque la misma respuesta no se le haya ocurrido que puede obtener el nombre de usuario a través del contexto de seguridad y no tener que enviarlo a través de la URL o el cuerpo, lo que me pone nervioso.

orbe
fuente
4
Quizás reset-passwordsuene como un verbo, pero puedes revertirlo fácilmente ( password-reset) para convertirlo en un sustantivo. Y si ha modelado su aplicación utilizando Event Sourcing o incluso si solo tiene algún tipo de auditoría, tiene sentido que realmente tenga una entidad real con este nombre e incluso podría permitir GET para que los usuarios o administradores vean historial (obviamente enmascarando el texto de la contraseña). No me pone nervioso en absoluto. Y en cuanto a elegir el nombre de usuario automáticamente en el lado del servidor, puede hacerlo, pero entonces, ¿cómo maneja cosas como la administración / suplantación?
Aaronaught
1
No hay nada de malo en usar verbo en REST. Siempre que se use en lugares apropiados. Creo que esto es más un controlador que un recurso, y me las arreglo reset-passwordpara describir bien sus efectos.
Anders Östman
6

Creo que una mejor idea sería:

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)

Respecto al suministro de datos:

  • Para restablecer la contraseña actual

    • el correo electrónico debe incluirse en el cuerpo.
  • Para crear una nueva contraseña (después de restablecer)

    • La nueva contraseña, el código de activación y la ID de correo electrónico deben figurar en el cuerpo.
  • Para actualizar la contraseña (para usuarios registrados)

    • contraseña anterior, la nueva contraseña debe mencionarse en el cuerpo.
    • UserId en Params.
    • Auth Token en los encabezados.
codigo
fuente
2
Como se comentó en otras respuestas, "DELETE / api / v1 / account / password" es una mala idea, ya que cualquiera podría restablecer la contraseña de cualquier persona.
Máximo
Necesitamos una identificación de correo electrónico registrada para restablecer la contraseña. Las posibilidades de conocer el ID de correo electrónico de un usuario desconocido son muy sombrías a menos que estemos ejecutando un sitio como Facebook y tengamos toneladas de ID de correos electrónicos recopilados por cualquier medio. Luego, las políticas de seguridad se definirán en consecuencia. ¿Cuál es tu sugerencia para restablecer la contraseña de alguien?
codesnooker
5

Hay algunas consideraciones a tener en cuenta:

Los restablecimientos de contraseña no son idempotentes

Un cambio de contraseña afecta los datos utilizados como credenciales para realizarlo, lo que, como resultado, podría invalidar futuros intentos si la solicitud simplemente se repite literalmente mientras las credenciales almacenadas han cambiado. Por ejemplo, si se usa un token de restablecimiento temporal para permitir el cambio, como es habitual en una situación de contraseña olvidada, ese token debe caducar tras un cambio de contraseña exitoso, lo que nuevamente anula los intentos adicionales de replicar la solicitud. Por lo tanto un enfoque REST a un cambio de contraseña parece ser un trabajo más adecuado para POSTque PUT.

La identificación o el correo electrónico en la carga de datos probablemente sea redundante

Aunque eso no está en contra de REST y puede tener algún propósito especial, a menudo no es necesario especificar una identificación o dirección de correo electrónico para restablecer la contraseña. Piénselo, ¿por qué proporcionaría la dirección de correo electrónico como parte de los datos a una solicitud que se supone que debe pasar por la autenticación de una forma u otra? Si el usuario simplemente está cambiando su contraseña, debe autenticarse para poder hacerlo (a través de nombre de usuario: contraseña, correo electrónico: contraseña o token de acceso proporcionado a través de encabezados). Por lo tanto, tenemos acceso a su cuenta desde ese paso. Si hubieran olvidado su contraseña, se les habría proporcionado un token de restablecimiento temporal (por correo electrónico) que pueden usar específicamente como credenciales para realizar el cambio. Y en este caso, la autenticación mediante token debería ser suficiente para identificar su cuenta.

Teniendo todo lo anterior en consideración, aquí está lo que creo que es el esquema adecuado para un cambio de contraseña 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
fuente
Una afirmación de que un marcador de posición requiere información fuera de banda no es del todo cierto. Un tipo de medio especial puede describir la sintaxis y la semántica de ciertos elementos de una solicitud o respuesta. Por tanto, es posible que un tipo de medio defina que un URI contenido en un determinado campo puede definir un marcador de posición para ciertos datos y la semántica define además que un correo electrónico de usuario codificado o lo que no debe incluirse en lugar del marcador de posición. Los clientes y servidores que respeten ese tipo de medio seguirán cumpliendo con los principios de la arquitectura RESTful.
Roman Vottner
1
En cuanto a POSTvs. PUT RFC 7231 especifica que se puede lograr una actualización parcial a través de datos superpuestos de dos recursos, pero es cuestionable si algo como /v1/account/passwordrealmente compensa un buen recurso. Al igual POSTque el swiss-army-kniff de la Web que se puede utilizar si ninguno de los otros métodos es factible, considerar PATCHtambién podría ser una opción para establecer la nueva contraseña.
Roman Vottner
¿Qué pasa con la URL para solicitar el restablecimiento de la contraseña cuando no conocen su contraseña?
dan carter
2

No tendría algo que cambie la contraseña y les envíe una nueva si decide usar el método / users / {id} / password y se adhiere a su idea de que la solicitud es un recurso en sí mismo. es decir, / user-password-request / es el recurso, y se usa PUT, la información del usuario debe estar en el cuerpo. Sin embargo, no cambiaría la contraseña, enviaría un correo electrónico al usuario que contiene un enlace a una página que contiene un request_guid, que podría pasar junto con una solicitud a POST / user / {id} / password /? Request_guid = xxxxx

Eso cambiaría la contraseña y no permite que alguien mande a un usuario solicitando un cambio de contraseña.

Además, el PUT inicial podría fallar si hay una solicitud pendiente.

bpeikes
fuente
0

Actualizamos la contraseña del usuario registrado PUT / v1 / users / password: identifique la identificación del usuario mediante AccessToken.

No es seguro intercambiar la identificación de usuario. La API Restful debe identificar al usuario que usa AccessToken recibido en el encabezado HTTP.

Ejemplo en spring-boot

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