Autenticación mediante tokens¶
Para evitar que en cada llamada el usuario deba entregar su clave y password se usa un token, que no es más que una cadena de texto bien larga, encriptada con una clave. Según se hace el login, el servidor devuelve al cliente un token y el cliente en futuros accesos entrega ese token para certificar que está autenticado.
Configuraciones base¶
Para llevar a cabo este proceso de trabajar con tokens, necesitamos crear una serie de ficheros que nos lo permitan.
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Auth;
use Exception;
use Illuminate\Http\Request;
use Laravel\Sanctum\PersonalAccessToken;
class UserController extends Controller
{
function login(Request $request)
{
$validator = validator()->make(
$request->all(),
[
'email' => 'required', 'email',
'password' => 'required'
]
);
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
$credentials = $validator->valid();
if (auth()->attempt($credentials)) {
$token = auth()->user()->createToken('myapptoken')->plainTextToken;
session()->put('token', $token);
return response()->json(
[
'isLoggedIn' => true,
'token' => $token,
'user' => auth()->user(),
]
);
}
return response()->json('The username and/or password do not match', 422);
}
function checkToken() {
try{
[$id, $token] = explode('|', request('token'));
$tokenHash = hash('sha256', $token);
$tokenModel = PersonalAccessToken::where('token', $tokenHash)->first();
if($tokenModel->tokenable) {
return response()->json(
[
'isLoggedIn' => true,
'token' => $token,
'user' => auth()->user(),
]
);
}
} catch( Exception $e){}
return response()->json('Invalid user', 422);
}
}
Y modificamos el modelo del usuario:
<?php
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
...
}
Crear tokens¶
En el código del controlador que hemos creado en la línea 31 vemos como, gracias a tener el paquete Sanctum instalado, tenemos la creación de tokens:
$token = auth()->user()->createToken('myapptoken')->plainTextToken;
Consumir recurso protegido por token¶
A la hora de consumir un recurso protegido por token, debemos de tenerlo. Ahora vamos a ver la manera de hacerlo, para ello, retomamos el código de App.vue:
<script>
export default {
mounted(){
const config = {
headers: {
// El token ha de ser el que te de tu aplicación.
Authorization: 'Bearer 19|2Lu2juLgTCW6rnUATamDpagq0y93Nb2pry5t779W72358518'
}
}
axios.get('/api/user', config).then(response => {
console.log(response.data)
})
...
}
}
</script>
Con esto ya podríamos consumir la API, pero nuevamente, esto no es todo lo funcional que debería ya que debería ser más dinámico.
authorization bearer
La cabecera authorization bearer es un tipo de header de una petición HTTP que carga con información relevante al tipo de usuario con el objetivo de darle autoridad para ejecutar la acción requerida en la petición. En este orden de ideas, la cabecera authorization bearer suele contener el JWT o JSON Web Token que se genera cuando el usuario inicia sesión.
Manejo del token de autenticación¶
Para comenzar a hacer este proceso más dinámico, vamos a almacenar el token en nuestra sesión de Laravel. Nuevamente, en el código del controlador que hemos creado en la línea 32 vemos cómo se realiza esta acción:
session()->put('token', $token);
Ahora vamos a pasar toda esta información a Laravel y luego a Vue. Empezaremos modificando la vista principal de nuestra aplicación en Laravel:
@if (Auth::check())
<script>
window.Laravel = {!! json_encode([
'isLoggedIn' => true,
'user' => Auth::user(),
'token' => session('token'),
]) !!}
</script>
@else
<script>
window.Laravel = {!! json_encode([
'isLoggedIn' => false
]) !!}
</script>
@endif
Con esto ya tendríamos una variable global en la que apoyarnos a la hora de realizar el proyecto de manera más dinámica.
Crear objeto global de usuario en Vue¶
Para poder consumirlo en nuestro proyecto reactivo es interesante tener el usuario como un objeto global.
<script>
export default {
mounted(){
if (window.Laravel && window.Laravel.isLoggedIn) {
this.isLoggedIn = true
this.user = window.Laravel.user
this.token = window.Laravel.token
}
...
},data() {
return {
isLoggedIn: false,
user: '',
token: '',
urls: {
postUpload: '/api/post/upload/',
postPaginate: '/api/post/',
postPatch: '/api/post/',
postPost: '/api/post/',
postDelete: '/api/post/',
getPostBySlug: '/api/post/slug/',
getCategoriesAll: '/api/category/all',
tokenCheck: '/api/user/token-check',
}
}
},
}
Es importante tener en cuenta que los valores iniciales se han de poner en la parte de data()
. Esto que estamos haciendo es un híbrido entre el servidor y una aplicación SPA.
Una vez que tenemos el usuario autenticado y todo funcionando es interesante que en la petición de login redireccionemos:
<script>
...
submit() {
axios.get('sanctum/csrf-cookie').then(response => {
axios.post('/api/user/login', this.form).then(response => {
setTimeout(() => (window.location.href = '/vue'), 1500)
}).catch(error => {
this.errors.login = error.response.data
})
})
},
...
</script>
En la línea 6 vemos como tras realizar una respuesta correcta hace la redirección hacia la página de inicio. Nos faltaría controlar que en nuestros componentes esté o no logueado:
<script>
export default {
created() {
if (this.$root.isLoggedIn) {
this.$router.push({ name: 'list' })
}
}
...
}
</script>
Enviar token en las peticiones¶
Al tener el token definido en la base de la aplicación , App.vue
, debemos hacer referencia a $root
para poder trabajar con él. Ahora para cada sitio que queramos trabajar con el token, debemos mandarlo de la siguiente manera:
<script>
export default {
mounted(){
const config = {
headers: {
Authorization: `Bearer ${this.$root.token}`
}
}
...
}
}
</script>
Cookieless
El término cookieless nace para hacer referencia a la desaparición de las cookies de terceros.
El origen del término nace con su aplicación en el Reglamento General de Protección de datos de la Unión Europea en 2018. Con esta normativa, las empresas necesitan el consentimiento explícito de los usuarios para poder recolectar la información y que ésta pueda ser utilizada en las campañas. Y con la entrada en vigor en el mismo año de la Ley Orgánica de Protección de datos en Europa, se entiende que las cookies de terceros no cumplen con la reglamentación y atentan directamente contra la privacidad del usuario.
Manejo mediante cookie¶
Las cookies es una parte importante a la hora de almacenar información que nuestra aplicación pueda usar. Aunque como hemos visto, no todas son legales, las propias del proyecto sí lo son. Es importante tener en cuenta que todo lo que pueda ser susceptible de robo de información de otra persona se ha de hashear. Una de las principales ventajas (o no) que tienen las cookies es que se puede configurar la vida que tienen.
Para el manejo de cookies en vue nos hace falta Vue Cookies:
npm install vue3-cookies
Lo importamos en nuestro main.js
import { createApp } from "vue";
import Oruga from '@oruga-ui/oruga-next'
import VueCookies from 'vue3-cookies'
import '../../css/vue.css'
import '@oruga-ui/theme-oruga/dist/oruga-full.min.css'
import axios from "axios";
import App from "./App.vue"
import router from './router'
const app = createApp(App)
app.use(Oruga).use(router).use(VueCookies)
app.config.globalProperties.$axios = axios
window.axios = axios
app.mount("#app")
Lo siguiente es modificar el App.vue
para que trabaje con cookies
<script>
export default {
mounted() {
if (window.Laravel && window.Laravel.isLoggedIn) {
this.isLoggedIn = true
this.user = window.Laravel.user
this.token = window.Laravel.token
this.$root.setCookieAuth({
isLoggedIn: this.isLoggedIn,
token: this.token,
user: this.user,
})
} else {
const auth = this.$cookies.get('auth')
if (auth) {
this.isLoggedIn = true
this.user = auth.user
this.token = auth.token
this.$axios.post(this.$root.urls.tokenCheck, {
token: auth.token
}).then(() => {
console.log('tokenCheck')
}).catch(() => {
this.setCookieAuth('');
window.location.href = '/vue/login'
})
}
}
},
methods: {
setCookieAuth(data) {
this.$cookies.set('auth', data)
},
logout() {
const config = {
headers: {
Authorization: 'Bearer ' + this.token
}
}
axios.post('/api/user/logout', null, config)
.then(() => {
this.setCookieAuth("")
window.location.href = '/vue'
})
.catch(() => {
window.location.href = '/vue'
})
}
},
data() {
return {
isLoggedIn: false,
user: '',
token: '',
urls: {
postUpload: '/api/post/upload/',
postPaginate: '/api/post/',
postPatch: '/api/post/',
postPost: '/api/post/',
postDelete: '/api/post/',
getPostBySlug: '/api/post/slug/',
getCategoriesAll: '/api/category/all',
tokenCheck: '/api/user/token-check',
}
}
},
}
</script>
Y nuestro fichero de router.js
import { createRouter, createWebHistory } from "vue-router";
import { useCookies } from 'vue3-cookies'
const { cookies } = useCookies()
import List from './componets/ListComponent.vue'
import Save from './componets/SaveComponent.vue'
import Login from './componets/auth/LoginComponent.vue'
const routes = [
{
name: 'login',
path: '/vue/login',
component: Login
},
{
name: 'list',
path: '/vue',
component: List
},
{
name: 'save',
path: '/vue/save/:slug?',
component: Save
},
]
const router = createRouter({
history: createWebHistory(),
routes:routes
})
router.beforeEach(async(to, from, next) => {
const auth = cookies.get('auth')
if(!auth && to.name !== 'login'){
return next({name:'login'})
}
next()
})
export default router
Con esto tendríamos nuestro sistema híbrido de sesión con cookie funcionando. Si no quisiéramos uno de ellos bastaría con eliminarlo y la aplicación funcionaría con un sistema u otro, ya que los cambios al final son pequeños.
Actividad¶
PR 911. (RA9 / CE9e CE9h / IC2 / 5p) - Haz, mediante token, que los alumnos puedan consultar su nota exclusivamente.