Undefined variable: errors

inhalin·2021년 11월 23일
0

Laravel

목록 보기
4/7

문제

내용

  • client에서 내용 보여주는 URL : http://localhost:8080/test/jobs
  • auth 서버 api URL : http://locahost:8081/api/jobs

OAuth api 라우트 설정 중에 뜬 에러.

// routes\api.php

Route::get('/test', function (Request $request) {
    return $request->user();
})->middleware('auth:api');

원래는 validation에 실패하면 세션에서 $errors에 에러메시지를 담아서 넘겨주고 그 내용이 출력되어야 하는데, $errors 변수 자체가 정의되지 않았다는 에러를 뱉었다.

추측 원인

라라벨 버전이 올라가면서 middleware가 동작하는 방식이 바뀌었다고 한다.

//  Laravel 5.1 - app/Http/Kernel.php

protected $middleware = [
    \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
    \App\Http\Middleware\EncryptCookies::class,
    \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
    \Illuminate\Session\Middleware\StartSession::class,
    \Illuminate\View\Middleware\ShareErrorsFromSession::class,
    \App\Http\Middleware\VerifyCsrfToken::class,
];

위의 코드는 라라벨 5.1버전 app/Http/Kernel.php의 기본 설정값이다. 이 배열은 글로벌 HTTP middleware stack이다. 무슨 말이나면, 모든 request에서 저 클래스들이 동작한다는 거다.

그 중에 눈여겨 볼 것이 밑에서 두번째 \Illuminate\View\Middleware\ShareErrorsFromSession::class이다. 이걸 까보면 아래와 같은 동작을 하는 것을 확인할 수 있다.

public function handle($request, Closure $next)
{
    $this->view->share(
        'errors', $request->session()->get('errors') ?: new ViewErrorBag
    );
    
    return $next($request);
}

즉, 이 놈이 모든 뷰에서 $errors 변수를 만들어서 사용할 수 있게 해주는거다.

그런데 이게 버전업이 되면서 위치가 바뀌었다. 이제 global middleware는 메인터넌스만 체크하고, web이라는 middleware group이 생기면서 global middleware에 있던 대부분의 것들이 이곳으로 옮겨졌다.

protected $middleware = [
    \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
];

protected $middlewareGroups = [
    'web' => [
        \App\Http\Middleware\EncryptCookies::class,
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \App\Http\Middleware\VerifyCsrfToken::class,
    ],

    'api' => [
        'throttle:60,1',
    ],
];

(반쪽짜리)해결

두가지 방법으로 의외로 간단하게 해결이 가능하다. Undefined variable: errors 에러만 해결되고 $request->user()에 대한 값이 null이어서 정작 제일 중요한 $request->user()->jobs를 불러오려고 하면 Trying to get property 'jobs' of non-object 에러가 뜬다.

1. middleware group 지정

api 라우트를 설정해주는 api.php에서 middleware group을 지정해주면 해결된다.

Route::group(['middleware' => ['api']], function () {
    Route:: get('/test', function(Request $request) {
        return $request->user()->jobs;
    });
});

2. global middleware에 포함시키기(위치 옮기기)

  1. \Illuminate\View\Middleware\ShareErrorsFromSession::class$middleware에 포함시켜준다. (이게 좋은 방법인지는 모르겠는데 어쨌든 문제는 해결된다.)

  2. \Illuminate\Session\Middleware\StartSession::class도 포함해준다. 안그러면 Session store not set on request. 에러가 뜬다. 순서는 StartSession을 1번보다 먼저 써준다.

protected $middleware = [
    \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
    \Illuminate\Session\Middleware\StartSession::class,
    \Illuminate\View\Middleware\ShareErrorsFromSession::class,
];

protected $middlewareGroups = [
    (생략)
    // \Illuminate\View\Middleware\ShareErrorsFromSession::class,
];

실제 원인

선임한테도 도움을 구하고 다시 시도해봤는데 해결법을 혼자 찾지 못해서 결국 팀장님께 도움을 받았다.

먼저, 미들웨어를 제대로 타고 있는지 확인해보았다.

에러가 발생되는 현재 auth 서버의 api.php를 보면 'auth:api' 미들웨어를 타도록 설정해주었다.

Route::middleware('auth:api')->get('/jobs', function (Request $request) {
    return $request->user()->jobs;
});

이게 실제로 어디로 타고 넘어가는지 보기 위해 Kernel.php를 보면, \App\Http\Middleware\Authenticate로 넘어가는 걸 알 수 있다.

protected $routeMiddleware = [
    'auth' => \App\Http\Middleware\Authenticate::class,

Authenticate.php를 까서handle()에서 잘 처리되고 있는지 확인하기 위해 break point를 찍고 xdebug로 디버깅을 해보았다.

실제로 handle()에서는 잘 걸리는 것을 확인했지만, 로그인 인증이 제대로 되지 않고 분기에 걸려서 response()->view()로 넘어가고 있었다.

해결

회사에서 사용하는 프로젝트는 5.x에서 올라온 것이라 8.x의 기본설정값과 비교해보기 위해 새 프로젝트를 임의로 만들어주었다.

비교해보니 handle()의 인자값부터 달랐다. 기본설정에서는 세번째 인자로 ...$guards를 받고 있었다. 일단 handle()로 넘어오면 authenticate()에서 다시 $guards를 확인해서 다음 단계로 넘어가고 있다.

회사 프로젝트를 처음 만들때는 api일때를 고려하지 않았기 때문에 api 주소일때 authenticate()로 넘어가는 분기 조건을 추가해주고 거기에서 $guards를 체크하도록 수정해주었다.

public function handle($request, Closure $next, ...$guards)
{
    if ($request->is('api/*')) {
        $this->authenticate($request, $guards);
    } else {
        ...
    }
        
    return $next($request);
}

protected function authenticate($request, array $guards)
{
    if (empty($guards)) {
        $guards = [null];
    }

    foreach ($guards as $guard) {
        if ($this->auth->guard($guard)->check()) {
            return $this->auth->shouldUse($guard);
        }
    }

    throw new AuthenticationException('Unauthenticated.', $guards);
    }

이제 다시 client에서 결과를 보여줄 페이지를 새로고침해보면...

json으로 결과를 예쁘게 출력해준다!

xdebug로 디버깅, bash 열어서 로그 확인 등 자유롭게 할 수 있도록 공부할 것.

참고

https://stackoverflow.com/questions/34420590/laravel-5-2-validation-errors?rq=1

검색어
laravel route auth:api
laravel middleware api detect

0개의 댓글