laravel sanctum SPA 身份验证受保护的路由返回 "message" : "unauthenticated"

Posted

技术标签:

【中文标题】laravel sanctum SPA 身份验证受保护的路由返回 "message" : "unauthenticated"【英文标题】:laravel sanctum SPA authentication Protected routes return "message" : "unauthenticated"laravel sanctum SPA 身份验证受保护的路由返回 "message" : "unauthenticated" 【发布时间】:2021-03-14 20:56:21 【问题描述】:

我正在开发一个大型项目,该项目有一个用于 API 的 laravel 后端和一个单独的 SPA(vue-cli 脚手架)。 它们位于 laravel 项目在域 (m.m) 上运行的同一***域上,而 Vue Spa 在 (vue.m.m:8080) 上运行

我的问题是:

auth:sanctum protected routes does not work they return ( "message" : "Unauthenticated" )

什么有效:

route /api/login 在提供正确的密码和电子邮件时完美运行(它会发送响应 ("message" : "Login successful"))

我的代码 sn-ps:

.env(这只是基于文档所需的部分,由于安全问题我无法显示其他部分)

BROADCAST_DRIVER=log
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
SESSION_DRIVER=cookie
SESSION_LIFETIME=120
SESSION_DOMAIN=.m.m
SANCTUM_STATEFUL_DOMAINS=vue.m.m

后端

UserController => 用于身份验证

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use Illuminate\Support\Facades\Auth;
use App\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
class UserController extends Controller

    public function register(Request $request)
    
        $this->validator($request->all())->validate();

        $user = $this->create($request->all());

        $this->guard()->login($user);

        return response()->json(['user'=> $user,
                                  'message'=> 'registration successful'
                                ], 200);
    
      /**
     * Get a validator for an incoming registration request.
     *
     * @param  array  $data
     * @return \Illuminate\Contracts\Validation\Validator
     */
    protected function validator(array $data)
    
        return Validator::make($data, [
            'name' => ['required', 'string', 'max:255'],
            'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
            'password' => ['required', 'string', 'min:4', 'confirmed'],
        ]);
    

    /**
     * Create a new user instance after a valid registration.
     *
     * @param  array  $data
     * @return \App\User
     */
    protected function create(array $data)
    
        return User::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => Hash::make($data['password']),
        ]);
    
    protected function guard()
    
        return Auth::guard();
    

    public function login(Request $request)
    
        $credentials = $request->only('email', 'password');

        if (Auth::attempt($credentials)) 
            // Authentication passed...
            return response()->json(['message' => 'Login successful'], 200);
        
    

    public function logout()
    
        Auth::logout();
        return response()->json(['message' => 'Logged Out'], 200);
    

sactum.php

<?php

return [

    'stateful' => explode(',', env(
        'SANCTUM_STATEFUL_DOMAINS',
        'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1'
    )),
    'expiration' => null,
    'middleware' => [
        'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class,
        'encrypt_cookies' => \Illuminate\Cookie\Middleware\EncryptCookies::class, 
    ],

];

auth.php

<?php

return [
    'defaults' => [
        'guard' => 'web',
        'passwords' => 'users',
    ],
    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],

        'api' => [
            'driver' => 'token',
            'provider' => 'users',
            'hash' => false,
        ],
    ],
    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\Models\User::class,
        ],

        // 'users' => [
        //     'driver' => 'database',
        //     'table' => 'users',
        // ],
    ],
    'passwords' => [
        'users' => [
            'provider' => 'users',
            'table' => 'password_resets',
            'expire' => 60,
            'throttle' => 60,
        ],
    ],

    'password_timeout' => 10800,

];

用户模型

<?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 HasFactory, Notifiable,HasApiTokens;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

api.php

Route::middleware('auth:sanctum')->get('/user', function (Request $request) 
    return "protected route";
);

Route::post("/login","App\Http\Controllers\UserController@login");

cors.php(出于开发目的,我允许处理所有传入的请求)和(凭据设置为 true)

'paths' => ['*'],

'allowed_methods' => ['*'],

'allowed_origins' => ['*'],

'allowed_origins_patterns' => [],

'allowed_headers' => ['*'],

'exposed_headers' => [],

'max_age' => 0,

'supports_credentials' => true,

kernel.php

protected $middleware = [
        // \App\Http\Middleware\TrustHosts::class,
        \Fruitcake\Cors\HandleCors::class,
        \App\Http\Middleware\TrustProxies::class,
        \Fruitcake\Cors\HandleCors::class,
        \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
    ];

 'api' => [
            \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
            'throttle:api',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

前端

axios

axios.defaults.withCredentials = true;
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
axios.defaults.baseURL = 'http://m.m/api';

Vue(这是处理发送登录请求的方法)

login(evt) 
      evt.preventDefault();
        axios.get("/sanctum/csrf-cookie",  baseURL: "http://m.m" ).then(() => 
          axios
            .post(
              "/login",
               email: "john@doe.com", password: "password" ,
            )
            .then(() => 
              const res = axios.get("/user");
              console.log(res.data);
            );
        );
      ;
    

我从浏览器获得的信息 network tab from chrome

这是作为 cookie 存储在浏览器中的内容 (image)

【问题讨论】:

【参考方案1】:

我遇到了同样的问题。我解决了以下问题:

在 api 路由中定义

Route::get('/unauthenticated', function () 
    return response()->json(["message" : "unauthenticated"]);
)->name('api.unauthenticated');

现在在Authenticate.php中间件的方法redirectTo中添加这一行

if ($request->is('api/*') || $request->is('api'))
 return route('api.unauthenticated');

就是这样。 它对我有用

【讨论】:

以上是关于laravel sanctum SPA 身份验证受保护的路由返回 "message" : "unauthenticated"的主要内容,如果未能解决你的问题,请参考以下文章

Sanctum 和 Postman 的 SPA 身份验证问题

如何使用 Laravel Sanctum 处理 SPA 和基于令牌的身份验证

用于非 SPA 的 Laravel Sanctum API 令牌

Laravel Sanctum Token API 身份验证在 Postman 中不起作用

Laravel 8 Sanctum SPA Auth - 未按要求设置会话存储

Laravel Sanctum 返回 404 而不是未经身份验证的 401