Laravel Passport
번역일: 2026년 6월 21일
Laravel Passport
- 소개
- 설치
- 설정
- 액세스 토큰 발급
- PKCE를 사용한 인가 코드 그랜트
- 패스워드 그랜트 토큰
- 암묵적 그랜트 토큰
- 클라이언트 자격증명 그랜트 토큰
- 개인 액세스 토큰
- 라우트 보호
- 토큰 스코프
- JavaScript에서 API 사용하기
- 이벤트
- 테스트
소개
Laravel Passport는 Laravel 애플리케이션에 완전한 OAuth2 서버 기능을 빠르게 추가할 수 있도록 도와주는 패키지입니다. Andy Millington과 Simon Hamp가 관리하는 League OAuth2 server 위에 구축되어 있습니다.
WARNING
이 문서는 독자가 OAuth2의 기본 개념을 이미 알고 있다고 가정합니다. OAuth2에 대해 잘 모른다면, 계속 읽기 전에 OAuth2의 일반적인 용어와 기능을 먼저 익혀두세요.
Passport vs Sanctum
시작하기 전에 애플리케이션에 Laravel Passport와 Laravel Sanctum 중 어느 것이 더 적합한지 검토해 보세요.
애플리케이션이 OAuth2를 반드시 지원해야 한다면 Laravel Passport를 사용하세요.
반면, SPA(싱글 페이지 애플리케이션), 모바일 앱 인증, 또는 단순 API 토큰 발급이 목적이라면 Laravel Sanctum을 권장합니다. Sanctum은 OAuth2를 지원하지 않지만, 훨씬 간단한 API 인증 개발 경험을 제공합니다.
NOTE
요약하면: 외부 서드파티 클라이언트가 여러분의 API에 접근하는 표준 OAuth2 플로우가 필요하면 Passport, 자체 프론트엔드나 모바일 앱의 인증만 처리하면 된다면 Sanctum을 선택하세요.
설치
Composer로 Passport를 설치합니다:
composer require laravel/passportPassport의 서비스 프로바이더는 자체 데이터베이스 마이그레이션 디렉토리를 등록합니다. 패키지 설치 후 마이그레이션을 실행하면 OAuth2 클라이언트와 액세스 토큰을 저장하는 데 필요한 테이블이 생성됩니다:
php artisan migrate다음으로 passport:install Artisan 명령어를 실행합니다. 이 명령어는 보안 액세스 토큰 생성에 필요한 암호화 키를 만들고, "personal access" 및 "password grant" 클라이언트도 함께 생성합니다:
php artisan passport:installNOTE
Passport Client 모델의 기본 키로 자동 증가 정수 대신 UUID를 사용하려면 --uuids 옵션을 사용하여 설치하세요.
passport:install 명령어 실행 후, App\Models\User 모델에 Laravel\Passport\HasApiTokens 트레이트를 추가합니다. 이 트레이트는 인증된 사용자의 토큰과 스코프를 확인하는 헬퍼 메서드를 제공합니다. 기존에 Laravel\Sanctum\HasApiTokens 트레이트를 사용 중이었다면 제거하세요:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
}마지막으로 config/auth.php 설정 파일에서 api 인증 가드를 정의하고 driver를 passport로 지정합니다. 이렇게 하면 API 요청 인증 시 Passport의 TokenGuard가 사용됩니다:
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],클라이언트 UUID 사용
--uuids 옵션을 붙여 passport:install 명령어를 실행하면 Passport Client 모델의 기본 키로 UUID를 사용합니다. 실행 후에는 Passport 기본 마이그레이션을 비활성화하는 방법에 대한 안내가 출력됩니다:
php artisan passport:install --uuidsPassport 배포하기
애플리케이션 서버에 Passport를 처음 배포할 때는 passport:keys 명령어를 실행해야 합니다. 이 명령어는 액세스 토큰 생성에 필요한 암호화 키를 생성합니다. 생성된 키는 일반적으로 소스 컨트롤에 포함시키지 않습니다:
php artisan passport:keys필요하다면 Passport 키를 로드할 경로를 직접 지정할 수 있습니다. Passport::loadKeysFrom 메서드를 사용하며, 보통 App\Providers\AuthServiceProvider의 boot 메서드에서 호출합니다:
/**
* 인증/인가 서비스를 등록합니다.
*/
public function boot(): void
{
Passport::loadKeysFrom(__DIR__.'/../secrets/oauth');
}환경 변수에서 키 로드하기
또는 vendor:publish Artisan 명령어로 Passport 설정 파일을 퍼블리시한 뒤:
php artisan vendor:publish --tag=passport-config암호화 키를 환경 변수로 정의하여 로드할 수 있습니다:
PASSPORT_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
<private key here>
-----END RSA PRIVATE KEY-----"
PASSPORT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----
<public key here>
-----END PUBLIC KEY-----"마이그레이션 커스터마이징
Passport의 기본 마이그레이션을 사용하지 않으려면 App\Providers\AppServiceProvider의 register 메서드에서 Passport::ignoreMigrations를 호출하세요. 그런 다음 아래 명령어로 기본 마이그레이션 파일을 내보내 원하는 대로 수정할 수 있습니다:
php artisan vendor:publish --tag=passport-migrationsPassport 업그레이드
Passport의 새로운 메이저 버전으로 업그레이드할 때는 반드시 업그레이드 가이드를 주의 깊게 검토하세요.
설정
클라이언트 시크릿 해싱
클라이언트 시크릿을 데이터베이스에 해시 처리하여 저장하려면 App\Providers\AuthServiceProvider의 boot 메서드에서 Passport::hashClientSecrets를 호출합니다:
use Laravel\Passport\Passport;
Passport::hashClientSecrets();활성화하면 클라이언트 시크릿의 평문 값은 생성 직후에만 사용자에게 표시됩니다. 평문 시크릿은 데이터베이스에 저장되지 않으므로, 분실 시 복구가 불가능합니다.
토큰 유효기간
기본적으로 Passport는 1년 후 만료되는 장기 액세스 토큰을 발급합니다. 유효기간을 조정하려면 tokensExpireIn, refreshTokensExpireIn, personalAccessTokensExpireIn 메서드를 사용합니다. App\Providers\AuthServiceProvider의 boot 메서드에서 호출하세요:
/**
* 인증/인가 서비스를 등록합니다.
*/
public function boot(): void
{
Passport::tokensExpireIn(now()->addDays(15));
Passport::refreshTokensExpireIn(now()->addDays(30));
Passport::personalAccessTokensExpireIn(now()->addMonths(6));
}WARNING
Passport 데이터베이스 테이블의 expires_at 컬럼은 표시 목적으로만 사용되며 읽기 전용입니다. 실제 만료 정보는 서명되고 암호화된 토큰 안에 저장됩니다. 토큰을 강제로 무효화하려면 토큰 폐기를 사용하세요.
기본 모델 재정의
Passport가 내부적으로 사용하는 모델을 확장할 수 있습니다. 해당 Passport 모델을 상속하는 커스텀 모델을 정의하세요:
use Laravel\Passport\Client as PassportClient;
class Client extends PassportClient
{
// ...
}모델을 정의한 후 App\Providers\AuthServiceProvider의 boot 메서드에서 Passport에 커스텀 모델을 등록합니다:
use App\Models\Passport\AuthCode;
use App\Models\Passport\Client;
use App\Models\Passport\PersonalAccessClient;
use App\Models\Passport\RefreshToken;
use App\Models\Passport\Token;
/**
* 인증/인가 서비스를 등록합니다.
*/
public function boot(): void
{
Passport::useTokenModel(Token::class);
Passport::useRefreshTokenModel(RefreshToken::class);
Passport::useAuthCodeModel(AuthCode::class);
Passport::useClientModel(Client::class);
Passport::usePersonalAccessClientModel(PersonalAccessClient::class);
}라우트 재정의
Passport가 등록하는 라우트를 커스터마이징하려면, 먼저 AppServiceProvider의 register 메서드에서 Passport::ignoreRoutes를 호출하여 Passport의 기본 라우트 등록을 비활성화합니다:
use Laravel\Passport\Passport;
/**
* 애플리케이션 서비스를 등록합니다.
*/
public function register(): void
{
Passport::ignoreRoutes();
}그런 다음 Passport의 라우트 파일에 정의된 라우트를 애플리케이션의 routes/web.php에 복사하여 원하는 대로 수정합니다:
Route::group([
'as' => 'passport.',
'prefix' => config('passport.path', 'oauth'),
'namespace' => '\Laravel\Passport\Http\Controllers',
], function () {
// Passport 라우트...
});액세스 토큰 발급
OAuth2에서 인가 코드 방식은 가장 널리 사용되는 그랜트 타입입니다. 클라이언트 애플리케이션이 사용자를 서버로 리다이렉트하면, 사용자는 액세스 토큰 발급 요청을 승인하거나 거부합니다.
클라이언트 관리
여러분의 API와 연동하는 애플리케이션을 개발하는 개발자는 먼저 자신의 앱을 "클라이언트"로 등록해야 합니다. 일반적으로 앱 이름과 인가 승인 후 리다이렉트될 URL을 제공하는 방식으로 등록합니다.
`passport:client` 명령어
클라이언트를 생성하는 가장 간단한 방법은 passport:client Artisan 명령어를 사용하는 것입니다. OAuth2 기능을 테스트하는 용도로 활용할 수 있으며, 명령어 실행 시 클라이언트 정보를 입력하면 클라이언트 ID와 시크릿이 발급됩니다:
php artisan passport:client리다이렉트 URL 여러 개 지정하기
클라이언트에 여러 리다이렉트 URL을 허용하려면 URL 입력 프롬프트에서 쉼표로 구분하여 입력하세요. 쉼표가 포함된 URL은 URL 인코딩해야 합니다:
http://example.com/callback,http://examplefoo.com/callbackJSON API
사용자가 직접 client 명령어를 사용할 수는 없으므로, Passport는 클라이언트 생성·수정·삭제를 위한 JSON API를 제공합니다. 이를 자체 프론트엔드와 연동하여 사용자가 클라이언트를 관리할 수 있는 대시보드를 만들 수 있습니다.
아래에서는 Axios를 사용하여 각 API 엔드포인트를 호출하는 예시를 보여줍니다. 이 JSON API는 web 및 auth 미들웨어로 보호되므로 외부에서는 호출할 수 없으며, 자체 애플리케이션 내에서만 사용 가능합니다.
`GET /oauth/clients`
인증된 사용자의 모든 클라이언트를 반환합니다. 사용자가 자신의 클라이언트를 목록에서 확인하고 수정·삭제할 때 활용합니다:
axios.get('/oauth/clients')
.then(response => {
console.log(response.data);
});`POST /oauth/clients`
새 클라이언트를 생성합니다. 클라이언트 name과 redirect URL이 필요합니다. 생성된 클라이언트에는 클라이언트 ID와 시크릿이 발급됩니다:
const data = {
name: '클라이언트 이름',
redirect: 'http://example.com/callback'
};
axios.post('/oauth/clients', data)
.then(response => {
console.log(response.data);
})
.catch(response => {
// 에러 처리...
});`PUT /oauth/clients/{client-id}`
클라이언트 정보를 수정합니다. name과 redirect URL이 필요합니다:
const data = {
name: '새 클라이언트 이름',
redirect: 'http://example.com/callback'
};
axios.put('/oauth/clients/' + clientId, data)
.then(response => {
console.log(response.data);
})
.catch(response => {
// 에러 처리...
});`DELETE /oauth/clients/{client-id}`
클라이언트를 삭제합니다:
axios.delete('/oauth/clients/' + clientId)
.then(response => {
// ...
});토큰 요청
인가를 위한 리다이렉트
클라이언트가 생성되면, 개발자는 클라이언트 ID와 시크릿으로 인가 코드 및 액세스 토큰을 요청할 수 있습니다. 클라이언트 애플리케이션은 먼저 애플리케이션의 /oauth/authorize 라우트로 사용자를 리다이렉트합니다:
use Illuminate\Http\Request;
use Illuminate\Support\Str;
Route::get('/redirect', function (Request $request) {
$request->session()->put('state', $state = Str::random(40));
$query = http_build_query([
'client_id' => 'client-id',
'redirect_uri' => 'http://third-party-app.com/callback',
'response_type' => 'code',
'scope' => '',
'state' => $state,
// 'prompt' => '', // "none", "consent", "login" 중 선택
]);
return redirect('http://passport-app.test/oauth/authorize?'.$query);
});prompt 파라미터로 인증 동작을 제어할 수 있습니다:
none: 사용자가 이미 로그인되어 있지 않으면 항상 인증 오류를 반환합니다.consent: 이전에 모든 스코프를 승인했더라도 항상 인가 승인 화면을 표시합니다.login: 기존 세션이 있어도 항상 재로그인을 요구합니다.
prompt를 지정하지 않으면, 요청된 스코프에 대해 이전에 인가하지 않은 경우에만 인가 화면이 표시됩니다.
NOTE
/oauth/authorize 라우트는 Passport가 자동으로 등록합니다. 수동으로 정의할 필요가 없습니다.
요청 승인
인가 요청을 받으면 Passport는 prompt 파라미터 값에 따라 자동으로 응답하거나, 사용자에게 승인·거부 화면을 표시합니다. 사용자가 승인하면 클라이언트가 지정한 redirect_uri로 리다이렉트됩니다. redirect_uri는 클라이언트 생성 시 등록한 redirect URL과 일치해야 합니다.
인가 승인 화면을 커스터마이징하려면 vendor:publish 명령어로 Passport의 뷰를 퍼블리시하세요. 퍼블리시된 뷰는 resources/views/vendor/passport 디렉토리에 위치합니다:
php artisan vendor:publish --tag=passport-views자사 클라이언트(first-party client)처럼 인가 프롬프트를 건너뛰고 싶은 경우, Client 모델을 확장하여 skipsAuthorization 메서드를 정의할 수 있습니다. true를 반환하면 클라이언트가 자동으로 승인되고 사용자는 즉시 redirect_uri로 이동합니다(단, 클라이언트 애플리케이션이 명시적으로 prompt 파라미터를 설정한 경우는 제외):
<?php
namespace App\Models\Passport;
use Laravel\Passport\Client as BaseClient;
class Client extends BaseClient
{
/**
* 이 클라이언트가 인가 프롬프트를 건너뛸지 여부를 결정합니다.
*/
public function skipsAuthorization(): bool
{
return $this->firstParty();
}
}인가 코드를 액세스 토큰으로 교환
사용자가 인가 요청을 승인하면 클라이언트 애플리케이션으로 리다이렉트됩니다. 클라이언트는 먼저 state 파라미터를 리다이렉트 전에 저장해둔 값과 비교하여 검증합니다. 검증이 통과되면 애플리케이션에 POST 요청을 보내 액세스 토큰을 요청합니다:
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
Route::get('/callback', function (Request $request) {
$state = $request->session()->pull('state');
throw_unless(
strlen($state) > 0 && $state === $request->state,
InvalidArgumentException::class,
'유효하지 않은 state 값입니다.'
);
$response = Http::asForm()->post('http://passport-app.test/oauth/token', [
'grant_type' => 'authorization_code',
'client_id' => 'client-id',
'client_secret' => 'client-secret',
'redirect_uri' => 'http://third-party-app.com/callback',
'code' => $request->code,
]);
return $response->json();
});/oauth/token 라우트는 access_token, refresh_token, expires_in을 포함한 JSON 응답을 반환합니다. expires_in은 액세스 토큰이 만료되기까지의 초(second) 단위 시간입니다.
NOTE
/oauth/authorize와 마찬가지로 /oauth/token 라우트도 Passport가 자동으로 등록합니다. 수동으로 정의할 필요가 없습니다.
JSON API
Passport는 인가된 액세스 토큰 관리를 위한 JSON API도 제공합니다. 이를 자체 프론트엔드와 연동하면 사용자가 자신의 토큰을 관리하는 대시보드를 만들 수 있습니다. 이 API는 web 및 auth 미들웨어로 보호됩니다.
`GET /oauth/tokens`
인증된 사용자가 생성한 모든 인가된 액세스 토큰을 반환합니다. 토큰 목록을 표시하고 폐기하는 데 활용합니다:
axios.get('/oauth/tokens')
.then(response => {
console.log(response.data);
});`DELETE /oauth/tokens/{token-id}`
액세스 토큰 및 연관된 리프레시 토큰을 폐기합니다:
axios.delete('/oauth/tokens/' + tokenId);토큰 갱신
단기 액세스 토큰을 발급하는 경우, 사용자는 액세스 토큰 발급 시 함께 제공된 리프레시 토큰으로 토큰을 갱신해야 합니다:
use Illuminate\Support\Facades\Http;
$response = Http::asForm()->post('http://passport-app.test/oauth/token', [
'grant_type' => 'refresh_token',
'refresh_token' => 'the-refresh-token',
'client_id' => 'client-id',
'client_secret' => 'client-secret',
'scope' => '',
]);
return $response->json();응답에는 access_token, refresh_token, expires_in이 포함됩니다.
토큰 폐기
Laravel\Passport\TokenRepository의 revokeAccessToken 메서드로 토큰을 폐기할 수 있습니다. 연관된 리프레시 토큰