Passport
업데이트됨번역일: 2026년 9월 10일
이 페이지는 원문이 업데이트되어 번역이 갱신되었습니다.
- 원문 수정
- 2026년 9월 2일
- 번역 갱신
- 2026년 9월 10일
Passport
소개
Laravel Passport는 Laravel 애플리케이션을 위한 완전한 OAuth2 서버 구현체를 몇 분 만에 제공해주는 패키지입니다. Passport는 Andy Millington과 Simon Hamp가 관리하는 League OAuth2 server 위에 구축되었습니다.
NOTE
이 문서는 여러분이 OAuth2에 이미 어느 정도 익숙하다는 것을 전제로 합니다. OAuth2에 대해 잘 모른다면, 본격적으로 문서를 읽기 전에 OAuth2에서 사용되는 일반 용어(예: "authorization code", "access token", "grant" 등)에 대해 먼저 학습해두는 것을 권장합니다.
Passport와 Sanctum, 어떤 것을 선택해야 할까?
인증 기능을 개발하기 전, Passport가 여러분의 상황에 실제로 필요한지 아니면 Laravel Sanctum으로 충분한지 먼저 확인해보는 것이 좋습니다. 애플리케이션이 반드시 OAuth2를 지원해야 하는 경우가 아니라면, 대부분의 경우 Sanctum이 더 간단하고 관리하기 쉬운 인증 솔루션입니다. 실제로 Passport와 Sanctum 모두 동일한 애플리케이션 내에서 함께 사용할 수도 있습니다.
Sanctum은 모바일 애플리케이션, SPA(단일 페이지 애플리케이션), 또는 API 클라이언트에 토큰을 발급하고자 할 때 우선적으로 고려해야 할 패키지입니다. Sanctum은 스코프(scope)나 능력(ability), 그리고 이러한 능력을 검사하는 미들웨어를 함께 지원하지만, OAuth2는 지원하지 않습니다. 만약 OAuth2 호환 인증 서버가 필요하다면 Laravel Passport가 정답입니다.
간단한 API 인증만 필요하다면 굳이 OAuth2의 복잡함을 감수할 필요는 없습니다. 대부분의 모바일 애플리케이션, SPA, API 인증 시나리오는 OAuth2를 사용하지 않는 Laravel Sanctum 패키지만으로도 충분히 처리할 수 있습니다.
NOTE
예를 들어, 국내 스타트업이 자체 모바일 앱과 웹 SPA에서만 사용할 API를 만드는 경우라면 Sanctum으로 충분한 경우가 대부분입니다. 반면 외부 파트너사에게 "우리 서비스에 로그인해서 API를 사용할 수 있는 권한"을 표준화된 방식으로 위임해야 한다면(예: 제3자 개발자에게 OAuth 앱 등록 기능을 제공하는 플랫폼), Passport가 필요한 시나리오에 해당합니다.
설치
다음 명령어로 Laravel Passport를 설치할 수 있습니다.
php artisan install:api --passport이 명령어는 애플리케이션이 OAuth2 클라이언트와 액세스 토큰을 저장하는 데 필요한 추가 테이블을 생성하는 마이그레이션을 게시하고 실행합니다. 또한 보안 액세스 토큰 생성에 필요한 암호화 키도 함께 생성합니다.
이 명령어를 실행한 후에는 App\Models\User 모델에 Laravel\Passport\HasApiTokens 트레이트를 추가하세요. 이 트레이트는 인증된 사용자의 토큰과 스코프를 검사할 수 있는 몇 가지 헬퍼 메서드를 모델에 제공합니다.
<?php
namespace App\Models;
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 요청을 인증할 때 Laravel이 Passport의 TokenGuard를 사용하도록 지시하게 됩니다.
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],Passport 배포하기
Passport를 처음으로 애플리케이션 서버에 배포할 때는 passport:keys 명령어를 실행해야 할 가능성이 높습니다. 이 명령어는 Passport가 액세스 토큰을 생성하는 데 사용하는 암호화 키를 생성합니다. 생성된 키는 일반적으로 소스 관리(source control)에 포함하지 않습니다.
php artisan passport:keys필요하다면 Passport의 키가 로드될 경로를 지정할 수도 있습니다. 이때는 Passport::loadKeysFrom 메서드를 사용합니다. 보통 이 메서드는 애플리케이션의 App\Providers\AppServiceProvider 클래스에 있는 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-----
...
-----END RSA PRIVATE KEY-----"
PASSPORT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----"Passport 업그레이드하기
Passport의 새로운 메이저 버전으로 업그레이드할 때는 업그레이드 가이드를 반드시 꼼꼼히 확인해야 합니다.
설정
토큰 유효 기간(Token Lifetimes)
기본적으로 Passport는 1년 후에 만료되는 장기 유효 액세스 토큰을 발급합니다. 토큰의 유효 기간을 더 길게 또는 짧게 설정하고 싶다면 tokensExpireIn, refreshTokensExpireIn, personalAccessTokensExpireIn 메서드를 사용할 수 있습니다. 이 메서드들은 애플리케이션의 App\Providers\AppServiceProvider 클래스에 있는 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는 만료 정보를 서명되고 암호화된 토큰 내부에 저장합니다. 토큰을 무효화해야 한다면 토큰을 폐기(revoke)해야 합니다.
기본 모델 재정의하기
Passport가 내부적으로 사용하는 모델을 자유롭게 확장하여 자신만의 모델을 정의할 수 있습니다. 이를 위해서는 먼저 Passport가 제공하는 해당 모델을 상속(extend)하면 됩니다.
use Laravel\Passport\Client as PassportClient;
class Client extends PassportClient
{
// ...
}모델을 정의한 후에는, Laravel\Passport\Passport 클래스를 통해 Passport에게 커스텀 모델을 사용하도록 지시할 수 있습니다. 일반적으로 이 작업은 애플리케이션의 App\Providers\AppServiceProvider 클래스의 boot 메서드에서 수행합니다.
use App\Models\Passport\AuthCode;
use App\Models\Passport\Client;
use App\Models\Passport\DeviceCode;
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::useDeviceCodeModel(DeviceCode::class);
}라우트 재정의하기
Passport가 정의하는 라우트를 직접 커스터마이징하고 싶은 경우도 있을 수 있습니다. 이를 위해서는 먼저 App\Providers\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 라우트...
});Passport
소개
Laravel Passport는 여러분의 Laravel 애플리케이션에 완전한 OAuth2 서버 구현을 단 몇 분 만에 제공하는 패키지입니다. Passport는 Andy Millington과 Simon Hamp가 관리하는 League OAuth2 server 위에 구축되었습니다.
NOTE
이 문서는 여러분이 이미 OAuth2에 대해 어느 정도 알고 있다는 것을 전제로 작성되었습니다. OAuth2에 대해 전혀 모른다면, 계속 진행하기 전에 OAuth2의 일반적인 용어와 개념을 먼저 익혀두는 것을 권장합니다.
Passport vs Sanctum, 무엇을 선택해야 할까?
시작하기에 앞서, 여러분의 애플리케이션에 Laravel Passport와 Laravel Sanctum 중 어느 것이 더 적합한지 먼저 판단해보는 것이 좋습니다. 애플리케이션이 반드시 OAuth2를 지원해야 하는 경우라면 Laravel Passport를 사용해야 합니다.
반면, SPA(싱글 페이지 애플리케이션)나 모바일 애플리케이션을 인증하거나 단순히 API 토큰을 발급하려는 목적이라면 Laravel Sanctum을 사용하는 것이 좋습니다. Laravel Sanctum은 OAuth2를 지원하지는 않지만, 훨씬 더 간단한 방식으로 API 인증을 구현할 수 있게 해줍니다.
NOTE
실무에서 자주 헷갈려하는 부분인데, 단순히 "우리 서비스에 로그인한 사용자에게 API 토큰을 발급"하는 정도라면 Sanctum으로 충분한 경우가 대부분입니다. 반면 "제3자 애플리케이션이 사용자를 대신해 우리 API에 접근할 수 있도록 권한을 위임"해야 한다면 (예: 외부 서비스가 "OO으로 로그인" 버튼을 통해 우리 서비스의 데이터에 접근하는 경우) OAuth2, 즉 Passport가 필요합니다.
설치
Laravel Passport는 install:api Artisan 명령어로 설치할 수 있습니다:
php artisan install:api --passport이 명령어는 애플리케이션이 OAuth2 클라이언트와 액세스 토큰을 저장하는 데 필요한 테이블을 생성하는 마이그레이션 파일을 게시하고 실행합니다. 또한 안전한 액세스 토큰을 생성하는 데 필요한 암호화 키도 함께 생성해줍니다.
install:api 명령어 실행이 끝나면, App\Models\User 모델에 Laravel\Passport\HasApiTokens 트레이트와 Laravel\Passport\Contracts\OAuthenticatable 인터페이스를 추가하세요. 이 트레이트는 인증된 사용자의 토큰과 스코프(scope)를 확인할 수 있는 몇 가지 헬퍼 메서드를 모델에 제공합니다:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\Contracts\OAuthenticatable;
use Laravel\Passport\HasApiTokens;
class User extends Authenticatable implements OAuthenticatable
{
use HasApiTokens, HasFactory, Notifiable;
}마지막으로, 애플리케이션의 config/auth.php 설정 파일에서 api 인증 가드를 정의하고 driver 옵션을 passport로 지정해야 합니다. 이렇게 하면 애플리케이션이 들어오는 API 요청을 인증할 때 Passport의 TokenGuard를 사용하게 됩니다:
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],NOTE
config/auth.php는 인증에 사용할 가드와 사용자 프로바이더를 정의하는 파일입니다. api 가드에 passport 드라이버를 지정해두면, auth:api 미들웨어가 적용된 라우트에서 자동으로 Passport 기반 토큰 인증이 동작합니다.
Passport 배포하기
애플리케이션 서버에 Passport를 처음 배포할 때는 passport:keys 명령어를 실행해야 하는 경우가 많습니다. 이 명령어는 Passport가 액세스 토큰을 생성하는 데 필요한 암호화 키를 만들어줍니다. 생성된 키는 일반적으로 소스 컨트롤(Git 등)에 포함시키지 않습니다:
php artisan passport:keys필요하다면 Passport 키를 불러올 경로를 직접 지정할 수도 있습니다. Passport::loadKeysFrom 메서드를 사용하면 되며, 보통 애플리케이션의 App\Providers\AppServiceProvider 클래스의 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-----"NOTE
회사 CI/CD 파이프라인(예: GitHub Actions, GitLab CI)이나 서버의 .env 파일에 키를 직접 저장해두면, 배포 시마다 passport:keys를 다시 실행할 필요 없이 동일한 키를 재사용할 수 있어 편리합니다.
Passport 업그레이드하기
Passport의 새로운 메이저 버전으로 업그레이드할 때는 반드시 업그레이드 가이드를 꼼꼼히 확인하시기 바랍니다.
Configuration (설정)
토큰 유효기간
Passport는 기본적으로 유효기간이 1년인 장기 액세스 토큰을 발급합니다. 토큰 유효기간을 더 길게 혹은 더 짧게 설정하고 싶다면 tokensExpireIn, refreshTokensExpireIn, personalAccessTokensExpireIn 메서드를 사용하면 됩니다. 이 메서드들은 애플리케이션의 App\Providers\AppServiceProvider 클래스의 boot 메서드에서 호출해야 합니다:
use Carbon\CarbonInterval;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Passport::tokensExpireIn(CarbonInterval::days(15));
Passport::refreshTokensExpireIn(CarbonInterval::days(30));
Passport::personalAccessTokensExpireIn(CarbonInterval::months(6));
}WARNING
Passport 데이터베이스 테이블의 expires_at 컬럼은 읽기 전용이며 단순히 화면에 표시하기 위한 용도로만 사용됩니다. 실제로 토큰을 발급할 때 Passport는 서명 및 암호화된 토큰 안에 만료 정보를 직접 저장합니다. 따라서 토큰을 무효화해야 한다면 컬럼 값을 수정하는 것이 아니라 토큰을 폐기(revoke)해야 합니다.
기본 모델 재정의하기
Passport가 내부적으로 사용하는 모델은 자유롭게 확장할 수 있습니다. 직접 모델을 정의하고 Passport의 해당 모델을 상속받으면 됩니다:
use Laravel\Passport\Client as PassportClient;
class Client extends PassportClient
{
// ...
}모델을 정의한 후에는 Laravel\Passport\Passport 클래스를 통해 Passport가 여러분이 만든 커스텀 모델을 사용하도록 지정할 수 있습니다. 보통 애플리케이션의 App\Providers\AppServiceProvider 클래스의 boot 메서드에서 이러한 커스텀 모델 정보를 Passport에 알려주면 됩니다:
use App\Models\Passport\AuthCode;
use App\Models\Passport\Client;
use App\Models\Passport\DeviceCode;
use App\Models\Passport\RefreshToken;
use App\Models\Passport\Token;
use Laravel\Passport\Passport;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Passport::useTokenModel(Token::class);
Passport::useRefreshTokenModel(RefreshToken::class);
Passport::useAuthCodeModel(AuthCode::class);
Passport::useClientModel(Client::class);
Passport::useDeviceCodeModel(DeviceCode::class);
}라우트 재정의하기
경우에 따라 Passport가 등록하는 라우트를 직접 커스터마이징하고 싶을 수 있습니다. 이를 위해서는 먼저 애플리케이션의 AppServiceProvider의 register 메서드에서 Passport::ignoreRoutes를 호출하여 Passport가 자체 등록하는 라우트를 무시하도록 설정해야 합니다:
use Laravel\Passport\Passport;
/**
* Register any application services.
*/
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 routes...
});NOTE
라우트를 직접 재정의하는 경우, Passport 버전이 업데이트될 때 원본 라우트 파일에 어떤 변경 사항이 있었는지 함께 확인하는 습관을 들이는 것이 좋습니다. 그렇지 않으면 새로운 그랜트 타입이나 엔드포인트가 추가되었을 때 이를 놓칠 수 있습니다.
Authorization Code Grant (인가 코드 그랜트)
OAuth2를 사용해 본 개발자라면 대부분 이 인가 코드(Authorization Code) 방식에 익숙할 것입니다. 인가 코드 그랜트를 사용하면 클라이언트 애플리케이션이 사용자를 여러분의 서버로 리다이렉트시키고, 사용자는 그곳에서 클라이언트에게 액세스 토큰을 발급할지 여부를 승인하거나 거부하게 됩니다.
시작하려면 먼저 "인가(authorization)" 화면을 어떻게 반환할지 Passport에게 알려줘야 합니다.
인가 화면의 렌더링 로직은 Laravel\Passport\Passport 클래스가 제공하는 메서드를 통해 자유롭게 커스터마이징할 수 있습니다. 보통 애플리케이션의 App\Providers\AppServiceProvider 클래스의 boot 메서드에서 다음과 같이 호출합니다:
use Inertia\Inertia;
use Laravel\Passport\Passport;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
// 뷰 이름을 지정하는 방법...
Passport::authorizationView('auth.oauth.authorize');
// 클로저를 지정하는 방법...
Passport::authorizationView(
fn ($parameters) => Inertia::render('Auth/OAuth/Authorize', [
'request' => $parameters['request'],
'authToken' => $parameters['authToken'],
'client' => $parameters['client'],
'user' => $parameters['user'],
'scopes' => $parameters['scopes'],
])
);
}Passport는 이 뷰를 반환하는 /oauth/authorize 라우트를 자동으로 정의합니다. auth.oauth.authorize 템플릿에는 인가를 승인하기 위해 passport.authorizations.approve 라우트로 POST 요청을 보내는 폼과, 인가를 거부하기 위해 passport.authorizations.deny 라우트로 DELETE 요청을 보내는 폼이 포함되어야 합니다. passport.authorizations.approve와 passport.authorizations.deny 라우트는 state, client_id, auth_token 필드를 필요로 합니다.
클라이언트 관리
여러분의 애플리케이션 API와 연동하려는 애플리케이션을 개발하는 외부 개발자는 "클라이언트"를 생성하여 여러분의 애플리케이션에 자신의 앱을 등록해야 합니다. 일반적으로 클라이언트 등록에는 애플리케이션 이름과, 사용자가 인가 요청을 승인한 뒤 리다이렉트될 URI가 필요합니다.
퍼스트파티(First-Party) 클라이언트
가장 간단한 클라이언트 생성 방법은 passport:client Artisan 명령어를 사용하는 것입니다. 이 명령어는 퍼스트파티 클라이언트를 생성하거나 OAuth2 기능을 테스트할 때 사용할 수 있습니다. passport:client 명령어를 실행하면 Passport가 클라이언트에 대한 추가 정보를 물어보고, 클라이언트 ID와 시크릿을 발급해줍니다:
php artisan passport:client클라이언트에 여러 개의 리다이렉트 URI를 허용하고 싶다면, passport:client 명령어가 URI를 물어볼 때 쉼표로 구분된 목록으로 입력하면 됩니다. URI 안에 쉼표가 포함되어 있다면 URI 인코딩을 해줘야 합니다:
https://third-party-app.com/callback,https://example.com/oauth/redirect서드파티(Third-Party) 클라이언트
애플리케이션 사용자는 passport:client 명령어를 직접 사용할 수 없으므로, Laravel\Passport\ClientRepository 클래스의 createAuthorizationCodeGrantClient 메서드를 사용해 특정 사용자를 위한 클라이언트를 등록할 수 있습니다:
use App\Models\User;
use Laravel\Passport\ClientRepository;
$user = User::find($userId);
// 주어진 사용자에게 속한 OAuth 앱 클라이언트를 생성...
$client = app(ClientRepository::class)->createAuthorizationCodeGrantClient(
user: $user,
name: 'Example App',
redirectUris: ['https://third-party-app.com/callback'],
confidential: false,
enableDeviceFlow: true
);
// 사용자에게 속한 모든 OAuth 앱 클라이언트를 조회...
$clients = $user->oauthApps()->get();createAuthorizationCodeGrantClient 메서드는 Laravel\Passport\Client 인스턴스를 반환합니다. 사용자에게는 $client->id를 클라이언트 ID로, $client->plainSecret을 클라이언트 시크릿으로 보여주면 됩니다.
토큰 요청
인가를 위한 리다이렉트
클라이언트가 생성되고 나면, 외부 개발자는 발급받은 클라이언트 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' => 'your-client-id',
'redirect_uri' => 'https://third-party-app.com/callback',
'response_type' => 'code',
'scope' => 'user:read orders:create',
'state' => $state,
// 'prompt' => '', // "none", "consent", "login" 중 하나
]);
return redirect('https://passport-app.test/oauth/authorize?'.$query);
});prompt 파라미터를 사용하면 Passport 애플리케이션의 인증 동작 방식을 지정할 수 있습니다.
prompt 값이 none이면, 사용자가 Passport 애플리케이션에 이미 인증되어 있지 않은 경우 항상 인증 오류가 발생합니다. 값이 consent이면, 연동 애플리케이션에 이전에 모든 스코프가 이미 승인된 상태라도 Passport는 항상 인가 승인 화면을 표시합니다. 값이 login이면, 이미 세션이 존재하더라도 Passport 애플리케이션은 항상 사용자에게 다시 로그인하도록 요구합니다.
prompt 값을 제공하지 않으면, 사용자가 요청된 스코프에 대해 이전에 연동 애플리케이션의 접근을 승인한 적이 없는 경우에만 인가 요청 화면이 표시됩니다.
NOTE
/oauth/authorize 라우트는 이미 Passport가 정의해 두었다는 점을 기억하세요. 이 라우트를 직접 정의할 필요는 없습니다.
요청 승인하기
인가 요청을 받으면 Passport는 (존재한다면) prompt 파라미터 값에 따라 자동으로 응답하며, 사용자가 인가 요청을 승인하거나 거부할 수 있는 템플릿을 보여줄 수 있습니다. 사용자가 요청을 승인하면 연동 애플리케이션이 지정한 redirect_uri로 다시 리다이렉트됩니다. 이때 redirect_uri는 클라이언트 생성 시 지정했던 redirect URL과 일치해야 합니다.
퍼스트파티 클라이언트를 인가하는 경우처럼, 때로는 인가 승인 화면을 건너뛰고 싶을 수 있습니다. 이럴 때는 Client 모델을 확장하고 skipsAuthorization 메서드를 정의하면 됩니다. skipsAuthorization이 true를 반환하면, 연동 애플리케이션이 인가 리다이렉트 시 prompt 파라미터를 명시적으로 설정하지 않은 이상 클라이언트가 즉시 승인되고 사용자는 바로 redirect_uri로 리다이렉트됩니다:
<?php
namespace App\Models\Passport;
use Illuminate\Contracts\Auth\Authenticatable;
use Laravel\Passport\Client as BaseClient;
class Client extends BaseClient
{
/**
* 클라이언트가 인가 승인 화면을 건너뛰어야 하는지 여부를 결정합니다.
*
* @param \Laravel\Passport\Scope[] $scopes
*/
public function skipsAuthorization(Authenticatable $user, array $scopes): bool
{
return $this->firstParty();
}
}인가 코드를 액세스 토큰으로 교환하기
사용자가 인가 요청을 승인하면 연동 애플리케이션으로 다시 리다이렉트됩니다. 이때 연동 애플리케이션은 먼저 리다이렉트 전에 저장해두었던 값과 state 파라미터를 비교해서 검증해야 합니다. 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,
'Invalid state value.'
);
$response = Http::asForm()->post('https://passport-app.test/oauth/token', [
'grant_type' => 'authorization_code',
'client_id' => 'your-client-id',
'client_secret' => 'your-client-secret',
'redirect_uri' => 'https://third-party-app.com/callback',
'code' => $request->code,
]);
return $response->json();
});이 /oauth/token 라우트는 access_token, refresh_token, expires_in 속성을 담은 JSON 응답을 반환합니다. expires_in 속성은 액세스 토큰이 만료되기까지 남은 시간(초)을 나타냅니다.
NOTE
/oauth/authorize 라우트와 마찬가지로, /oauth/token 라우트 역시 Passport가 미리 정의해 두었습니다. 직접 정의할 필요가 없습니다.
토큰 관리
Laravel\Passport\HasApiTokens 트레이트의 tokens 메서드를 사용해 사용자가 인가한 토큰들을 조회할 수 있습니다. 예를 들어, 사용자에게 서드파티 애플리케이션과의 연동 현황을 보여주는 대시보드를 만들 때 활용할 수 있습니다:
use App\Models\User;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Date;
use Laravel\Passport\Token;
$user = User::find($userId);
// 사용자의 유효한 모든 토큰을 조회...
$tokens = $user->tokens()
->where('revoked', false)
->where('expires_at', '>', Date::now())
->get();
// 사용자의 서드파티 OAuth 앱 클라이언트와의 연결 목록을 조회...
$connections = $tokens->load('client')
->reject(fn (Token $token) => $token->client->firstParty())
->groupBy('client_id')
->map(fn (Collection $tokens) => [
'client' => $tokens->first()->client,
'scopes' => $tokens->pluck('scopes')->flatten()->unique()->values()->all(),
'tokens_count' => $tokens->count(),
])
->values();토큰 갱신
애플리케이션이 수명이 짧은 액세스 토큰을 발급하는 경우, 사용자는 액세스 토큰 발급 시 함께 제공된 리프레시 토큰을 통해 액세스 토큰을 갱신해야 합니다:
use Illuminate\Support\Facades\Http;
$response = Http::asForm()->post('https://passport-app.test/oauth/token', [
'grant_type' => 'refresh_token',
'refresh_token' => 'the-refresh-token',
'client_id' => 'your-client-id',
'client_secret' => 'your-client-secret', // 컨피덴셜(confidential) 클라이언트만 필요...
'scope' => 'user:read orders:create',
]);
return $response->json();이 /oauth/token 라우트 역시 access_token, refresh_token, expires_in 속성을 담은 JSON 응답을 반환합니다. expires_in 속성은 액세스 토큰이 만료되기까지 남은 시간(초)을 나타냅니다.
토큰 폐기
Laravel\Passport\Token 모델의 revoke 메서드를 사용해 토큰을 폐기할 수 있습니다. 또한 Laravel\Passport\RefreshToken 모델의 revoke 메서드로 해당 토큰의 리프레시 토큰도 폐기할 수 있습니다:
use Laravel\Passport\Passport;
use Laravel\Passport\Token;
$token = Passport::token()->find($tokenId);
// 액세스 토큰 폐기...
$token->revoke();
// 토큰의 리프레시 토큰 폐기...
$token->refreshToken?->revoke();
// 사용자의 모든 토큰 폐기...
User::find($userId)->tokens()->each(function (Token $token) {
$token->revoke();
$token->refreshToken?->revoke();
});토큰 정리(Purge)
폐기되었거나 만료된 토큰은 데이터베이스에서 완전히 삭제하고 싶을 수 있습니다. Passport에는 이 작업을 도와주는 passport:purge Artisan 명령어가 포함되어 있습니다:
<h1 id="creating-a-device-authorization-grant-client">폐기되었거나 만료된 토큰, 인가 코드, 디바이스 코드를 모두 삭제...</h1>
php artisan passport:purge
<h1 id="requesting-device-authorization-grant-tokens">6시간 이상 만료된 토큰만 삭제...</h1>
php artisan passport:purge --hours=6
<h1 id="device-code">폐기된 토큰, 인가 코드, 디바이스 코드만 삭제...</h1>
php artisan passport:purge --revoked
<h1 id="user-code">만료된 토큰, 인가 코드, 디바이스 코드만 삭제...</h1>
php artisan passport:purge --expired애플리케이션의 routes/console.php 파일에 스케줄 작업을 등록해서 주기적으로 토큰을 자동 정리하도록 설정할 수도 있습니다:
use Illuminate\Support\Facades\Schedule;
Schedule::command('passport:purge')->hourly();Passport
PKCE를 사용하는 Authorization Code Grant
"PKCE(Proof Key for Code Exchange, 코드 교환 증명 키)"를 사용하는 Authorization Code Grant는 SPA(싱글 페이지 애플리케이션)나 모바일 애플리케이션이 여러분의 API에 안전하게 인증할 수 있도록 해주는 방식입니다. 클라이언트 시크릿을 안전하게 보관할 수 있다고 보장할 수 없는 환경이거나, 인가 코드(authorization code)가 공격자에게 가로채일 위험을 줄이고 싶을 때 이 그랜트 타입을 사용해야 합니다. 이 방식에서는 인가 코드를 액세스 토큰으로 교환할 때 클라이언트 시크릿 대신 "코드 검증자(code verifier)"와 "코드 챌린지(code challenge)"의 조합을 사용합니다.
클라이언트 생성하기
PKCE를 사용하는 Authorization Code Grant로 토큰을 발급하려면, 먼저 PKCE가 활성화된 클라이언트를 생성해야 합니다. passport:client Artisan 명령어에 --public 옵션을 추가해 생성할 수 있습니다.
php artisan passport:client --public토큰 요청하기
코드 검증자와 코드 챌린지
이 인가 그랜트 방식은 클라이언트 시크릿을 사용하지 않으므로, 개발자는 토큰을 요청하기 위해 코드 검증자와 코드 챌린지 조합을 직접 생성해야 합니다.
코드 검증자는 RFC 7636 명세에 정의된 대로, 영문자·숫자와 "-", ".", "_", "~" 문자를 조합한 43~128자 길이의 랜덤 문자열이어야 합니다.
코드 챌린지는 URL 및 파일명에 안전한 문자로 Base64 인코딩된 문자열이어야 합니다. 끝에 붙는 '=' 문자는 제거해야 하며, 줄바꿈이나 공백, 그 밖의 다른 문자가 포함되어서는 안 됩니다.
$encoded = base64_encode(hash('sha256', $codeVerifier, true));
$codeChallenge = strtr(rtrim($encoded, '='), '+/', '-_');인가를 위한 리다이렉션
클라이언트를 생성했다면, 클라이언트 ID와 생성한 코드 검증자·코드 챌린지를 사용해 애플리케이션으로부터 인가 코드와 액세스 토큰을 요청할 수 있습니다. 먼저 요청을 보내는(consuming) 애플리케이션에서 여러분의 애플리케이션의 /oauth/authorize 라우트로 리다이렉트 요청을 보내야 합니다.
use Illuminate\Http\Request;
use Illuminate\Support\Str;
Route::get('/redirect', function (Request $request) {
$request->session()->put('state', $state = Str::random(40));
$request->session()->put(
'code_verifier', $codeVerifier = Str::random(128)
);
$codeChallenge = strtr(rtrim(
base64_encode(hash('sha256', $codeVerifier, true))
, '='), '+/', '-_');
$query = http_build_query([
'client_id' => 'your-client-id',
'redirect_uri' => 'https://third-party-app.com/callback',
'response_type' => 'code',
'scope' => 'user:read orders:create',
'state' => $state,
'code_challenge' => $codeChallenge,
'code_challenge_method' => 'S256',
// 'prompt' => '', // "none", "consent", "login" 중 하나
]);
return redirect('https://passport-app.test/oauth/authorize?'.$query);
});인가 코드를 액세스 토큰으로 교환하기
사용자가 인가 요청을 승인하면, 요청을 보냈던 애플리케이션으로 다시 리다이렉트됩니다. 표준 Authorization Code Grant와 마찬가지로, 요청을 보낸 쪽에서는 리다이렉트 전에 저장해 두었던 값과 state 파라미터를 비교하여 검증해야 합니다.
state 파라미터가 일치한다면, 요청을 보낸 쪽에서는 여러분의 애플리케이션으로 POST 요청을 보내 액세스 토큰을 요청해야 합니다. 이 요청에는 사용자가 인가 요청을 승인했을 때 애플리케이션이 발급한 인가 코드와, 처음에 생성했던 코드 검증자가 함께 포함되어야 합니다.
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
Route::get('/callback', function (Request $request) {
$state = $request->session()->pull('state');
$codeVerifier = $request->session()->pull('code_verifier');
throw_unless(
strlen($state) > 0 && $state === $request->state,
InvalidArgumentException::class
);
$response = Http::asForm()->post('https://passport-app.test/oauth/token', [
'grant_type' => 'authorization_code',
'client_id' => 'your-client-id',
'redirect_uri' => 'https://third-party-app.com/callback',
'code_verifier' => $codeVerifier,
'code' => $request->code,
]);
return $response->json();
});NOTE
PKCE 방식은 보안을 위해 클라이언트 시크릿 대신 코드 검증자·코드 챌린지 쌍을 매 요청마다 새로 생성해서 사용합니다. 코드 검증자는 세션 등 안전한 저장소에 임시로 보관했다가, 토큰 교환 시점에만 사용하고 즉시 폐기하는 것이 좋습니다.
디바이스 인가 그랜트 (Device Authorization Grant)
OAuth2 디바이스 인가 그랜트는 TV, 게임 콘솔처럼 브라우저가 없거나 입력 수단이 제한된 기기가 "디바이스 코드"를 교환하여 액세스 토큰을 발급받을 수 있도록 해주는 방식입니다. 이 방식을 사용하면 디바이스 클라이언트는 사용자에게 컴퓨터나 스마트폰 같은 보조 기기로 서버에 접속하여 제공된 "사용자 코드"를 입력하고 접근 요청을 승인하거나 거부하도록 안내합니다.
NOTE
스마트 TV 앱에서 "화면에 표시된 코드를 스마트폰으로 입력해 주세요"라는 안내를 받아본 적이 있다면, 바로 이 디바이스 인가 그랜트 방식을 경험한 것입니다.
시작하려면 Passport에게 "사용자 코드" 화면과 "인가" 화면을 어떻게 반환할지 알려주어야 합니다.
인가 화면의 렌더링 로직은 Laravel\Passport\Passport 클래스가 제공하는 메서드를 통해 자유롭게 커스터마이징할 수 있습니다. 일반적으로 이 메서드는 애플리케이션의 App\Providers\AppServiceProvider 클래스에 있는 boot 메서드 안에서 호출합니다.
use Inertia\Inertia;
use Laravel\Passport\Passport;
/**
* 애플리케이션 서비스 부트스트랩
*/
public function boot(): void
{
// 뷰 이름을 지정하는 방식...
Passport::deviceUserCodeView('auth.oauth.device.user-code');
Passport::deviceAuthorizationView('auth.oauth.device.authorize');
// 클로저를 지정하는 방식...
Passport::deviceUserCodeView(
fn ($parameters) => Inertia::render('Auth/OAuth/Device/UserCode')
);
Passport::deviceAuthorizationView(
fn ($parameters) => Inertia::render('Auth/OAuth/Device/Authorize', [
'request' => $parameters['request'],
'authToken' => $parameters['authToken'],
'client' => $parameters['client'],
'user' => $parameters['user'],
'scopes' => $parameters['scopes'],
])
);
// ...
}Passport는 위 뷰들을 반환하는 라우트를 자동으로 정의합니다. auth.oauth.device.user-code 템플릿에는 passport.device.authorizations.authorize 라우트로 GET 요청을 보내는 폼이 포함되어야 합니다. 이때 passport.device.authorizations.authorize 라우트는 user_code 쿼리 파라미터를 필요로 합니다.
auth.oauth.device.authorize 템플릿에는 인가를 승인하기 위해 passport.device.authorizations.approve 라우트로 POST 요청을 보내는 폼과, 인가를 거부하기 위해 passport.device.authorizations.deny 라우트로 DELETE 요청을 보내는 폼이 포함되어야 합니다. passport.device.authorizations.approve와 passport.device.authorizations.deny 라우트는 state, client_id, auth_token 필드를 필요로 합니다.
디바이스 인가 그랜트 클라이언트 생성하기
디바이스 인가 그랜트로 토큰을 발급하려면, 먼저 디바이스 플로우가 활성화된 클라이언트를 생성해야 합니다. passport:client Artisan 명령어에 --device 옵션을 함께 사용하면 됩니다. 이 명령어는 퍼스트파티 디바이스 플로우 클라이언트를 생성하고 클라이언트 ID와 시크릿을 발급합니다:
php artisan passport:client --device또는 ClientRepository 클래스의 createDeviceAuthorizationGrantClient 메서드를 사용하여, 특정 사용자에게 소속된 서드파티 클라이언트를 등록할 수도 있습니다:
use App\Models\User;
use Laravel\Passport\ClientRepository;
$user = User::find($userId);
$client = app(ClientRepository::class)->createDeviceAuthorizationGrantClient(
user: $user,
name: 'Example Device',
confidential: false,
);토큰 요청하기
디바이스 코드 요청하기
클라이언트가 생성되면, 개발자는 클라이언트 ID를 사용해 애플리케이션으로부터 디바이스 코드를 요청할 수 있습니다. 먼저 디바이스는 애플리케이션의 /oauth/device/code 라우트로 POST 요청을 보내 디바이스 코드를 요청해야 합니다:
use Illuminate\Support\Facades\Http;
$response = Http::asForm()->post('https://passport-app.test/oauth/device/code', [
'client_id' => 'your-client-id',
'scope' => 'user:read orders:create',
]);
return $response->json();이 요청에 대한 응답으로 device_code, user_code, verification_uri, interval, expires_in 속성을 담은 JSON이 반환됩니다. expires_in 속성은 디바이스 코드가 만료되기까지 남은 초 단위 시간이며, interval 속성은 디바이스가 /oauth/token 라우트를 폴링(polling)할 때 요청 사이에 대기해야 하는 초 단위 시간입니다. 이 간격을 지키지 않으면 요청 제한(rate limit) 오류가 발생할 수 있습니다.
NOTE
/oauth/device/code 라우트는 Passport가 이미 정의해 두었으므로 별도로 라우트를 정의할 필요가 없습니다.
인증 URI와 사용자 코드 표시하기
디바이스 코드 요청이 성공하면, 디바이스는 사용자에게 다른 기기를 사용해 응답받은 verification_uri에 접속한 뒤 user_code를 입력하여 인가 요청을 승인하도록 안내해야 합니다.
토큰 요청 폴링하기
사용자가 별도 기기에서 접근을 승인(또는 거부)하는 동안, 원래 디바이스는 사용자가 언제 응답했는지 확인하기 위해 애플리케이션의 /oauth/token 라우트를 계속 폴링해야 합니다. 이때 디바이스 코드 요청 응답에 포함된 최소 폴링 interval 값을 지켜야 요청 제한 오류를 피할 수 있습니다:
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Sleep;
$interval = 5;
do {
Sleep::for($interval)->seconds();
$response = Http::asForm()->post('https://passport-app.test/oauth/token', [
'grant_type' => 'urn:ietf:params:oauth:grant-type:device_code',
'client_id' => 'your-client-id',
'client_secret' => 'your-client-secret', // 컨피덴셜 클라이언트에서만 필요...
'device_code' => 'the-device-code',
]);
if ($response->json('error') === 'slow_down') {
$interval += 5;
}
} while (in_array($response->json('error'), ['authorization_pending', 'slow_down']));
return $response->json();사용자가 인가 요청을 승인했다면, 이 응답에는 access_token, refresh_token, expires_in 속성을 담은 JSON이 반환됩니다. expires_in 속성은 액세스 토큰이 만료되기까지 남은 초 단위 시간을 나타냅니다.
Password Grant
WARNING
Password Grant 토큰 사용은 더 이상 권장하지 않습니다. 대신 OAuth2 Server가 현재 권장하는 그랜트 타입을 사용하는 것이 좋습니다.
OAuth2 Password Grant는 모바일 애플리케이션처럼 여러분이 직접 소유한(1st party) 클라이언트가 이메일 주소/사용자 이름과 비밀번호만으로 액세스 토큰을 발급받을 수 있도록 해줍니다. 덕분에 사용자가 OAuth2 인가 코드 리다이렉트 흐름 전체를 거치지 않아도, 1st party 클라이언트에 안전하게 액세스 토큰을 발급할 수 있습니다.
Password Grant를 사용하려면, 애플리케이션의 App\Providers\AppServiceProvider 클래스의 boot 메서드에서 enablePasswordGrant 메서드를 호출하세요.
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Passport::enablePasswordGrant();
}Password Grant 클라이언트 생성하기
Password Grant 방식으로 토큰을 발급하려면 먼저 Password Grant 클라이언트를 생성해야 합니다. passport:client Artisan 명령어에 --password 옵션을 붙여 실행하면 됩니다.
php artisan passport:client --password토큰 요청하기
그랜트를 활성화하고 Password Grant 클라이언트를 생성했다면, 사용자의 이메일 주소와 비밀번호를 담아 /oauth/token 라우트로 POST 요청을 보내 액세스 토큰을 발급받을 수 있습니다. 이 라우트는 Passport가 이미 등록해두었으므로 별도로 정의할 필요가 없습니다. 요청이 성공하면 서버는 JSON 응답에 access_token과 refresh_token을 담아 반환합니다.
use Illuminate\Support\Facades\Http;
$response = Http::asForm()->post('https://passport-app.test/oauth/token', [
'grant_type' => 'password',
'client_id' => 'your-client-id',
'client_secret' => 'your-client-secret', // 기밀 클라이언트에만 필요...
'username' => 'taylor@laravel.com',
'password' => 'my-password',
'scope' => 'user:read orders:create',
]);
return $response->json();NOTE
액세스 토큰은 기본적으로 만료 기간이 길게 설정됩니다. 필요하다면 최대 액세스 토큰 유효 기간을 설정할 수 있습니다.
모든 스코프 요청하기
Password Grant나 Client Credentials Grant를 사용할 때, 애플리케이션이 지원하는 모든 스코프에 대한 권한을 가진 토큰을 발급받고 싶을 수 있습니다. 이럴 때는 * 스코프를 요청하면 됩니다. * 스코프를 요청하면 해당 토큰 인스턴스의 can 메서드는 항상 true를 반환합니다. 이 스코프는 password 또는 client_credentials 그랜트로 발급된 토큰에만 부여할 수 있습니다.
use Illuminate\Support\Facades\Http;
$response = Http::asForm()->post('https://passport-app.test/oauth/token', [
'grant_type' => 'password',
'client_id' => 'your-client-id',
'client_secret' => 'your-client-secret', // 기밀 클라이언트에만 필요...
'username' => 'taylor@laravel.com',
'password' => 'my-password',
'scope' => '*',
]);사용자 프로바이더 커스터마이징하기
애플리케이션이 둘 이상의 인증 사용자 프로바이더를 사용하고 있다면, artisan passport:client --password 명령 실행 시 --provider 옵션을 지정하여 Password Grant 클라이언트가 어떤 사용자 프로바이더를 사용할지 명시할 수 있습니다. 이때 지정하는 프로바이더 이름은 애플리케이션의 config/auth.php 설정 파일에 정의된 유효한 프로바이더와 일치해야 합니다. 이후 미들웨어로 라우트를 보호하여 해당 가드에 지정된 프로바이더의 사용자만 인증되도록 제한할 수 있습니다.
사용자 이름(username) 필드 커스터마이징하기
Password Grant로 인증할 때, Passport는 기본적으로 인증 가능한 모델의 email 속성을 "username"으로 사용합니다. 하지만 모델에 findForPassport 메서드를 정의하면 이 동작을 원하는 대로 커스터마이징할 수 있습니다.
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\Bridge\Client;
use Laravel\Passport\Contracts\OAuthenticatable;
use Laravel\Passport\HasApiTokens;
class User extends Authenticatable implements OAuthenticatable
{
use HasApiTokens, Notifiable;
/**
* Find the user instance for the given username.
*/
public function findForPassport(string $username, Client $client): User
{
return $this->where('username', $username)->first();
}
}비밀번호 검증 로직 커스터마이징하기
Password Grant로 인증할 때, Passport는 기본적으로 모델의 password 속성을 사용하여 전달받은 비밀번호를 검증합니다. 만약 모델에 password 속성이 없거나 비밀번호 검증 로직을 직접 커스터마이징하고 싶다면, 모델에 validateForPassportPasswordGrant 메서드를 정의하면 됩니다.
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Hash;
use Laravel\Passport\Contracts\OAuthenticatable;
use Laravel\Passport\HasApiTokens;
class User extends Authenticatable implements OAuthenticatable
{
use HasApiTokens, Notifiable;
/**
* Validate the password of the user for the Passport password grant.
*/
public function validateForPassportPasswordGrant(string $password): bool
{
return Hash::check($password, $this->password);
}
}Implicit Grant (암묵적 부여)
WARNING
암묵적 부여(implicit grant) 방식의 토큰 사용은 더 이상 권장하지 않습니다. 대신 OAuth2 Server가 현재 권장하는 부여 방식을 사용하시기 바랍니다.
암묵적 부여는 인가 코드 부여(authorization code grant)와 비슷하지만, 인가 코드를 교환하는 과정 없이 토큰이 클라이언트에게 바로 반환된다는 차이가 있습니다. 이 방식은 클라이언트 자격 증명을 안전하게 저장하기 어려운 JavaScript 애플리케이션이나 모바일 애플리케이션에서 주로 사용됩니다. 이 부여 방식을 활성화하려면, 애플리케이션의 App\Providers\AppServiceProvider 클래스에 있는 boot 메서드에서 enableImplicitGrant 메서드를 호출하면 됩니다.
/**
* 애플리케이션의 서비스를 부트스트랩합니다.
*/
public function boot(): void
{
Passport::enableImplicitGrant();
}애플리케이션이 암묵적 부여 방식으로 토큰을 발급하려면, 먼저 암묵적 부여용 클라이언트를 생성해야 합니다. 이 클라이언트는 --implicit 옵션과 함께 passport:client Artisan 명령어를 실행하여 생성할 수 있습니다.
php artisan passport:client --implicit부여 방식을 활성화하고 암묵적 클라이언트를 생성했다면, 개발자는 해당 클라이언트 ID를 사용해 여러분의 애플리케이션에 액세스 토큰을 요청할 수 있습니다. 이를 사용하려는 애플리케이션(consuming application)에서는 아래와 같이 여러분의 애플리케이션의 /oauth/authorize 라우트로 리다이렉트 요청을 보내면 됩니다.
use Illuminate\Http\Request;
Route::get('/redirect', function (Request $request) {
$request->session()->put('state', $state = Str::random(40));
$query = http_build_query([
'client_id' => 'your-client-id',
'redirect_uri' => 'https://third-party-app.com/callback',
'response_type' => 'token',
'scope' => 'user:read orders:create',
'state' => $state,
// 'prompt' => '', // "none", "consent", "login" 중 하나
]);
return redirect('https://passport-app.test/oauth/authorize?'.$query);
});NOTE
/oauth/authorize 라우트는 Passport에서 이미 정의되어 있으므로, 별도로 직접 정의할 필요가 없습니다.
클라이언트 자격 증명 그랜트 (Client Credentials Grant)
클라이언트 자격 증명 그랜트는 머신 투 머신(machine-to-machine) 인증에 적합한 방식입니다. 예를 들어 API를 통해 유지보수 작업을 수행하는 스케줄링 작업(Job)에서 이 그랜트를 사용할 수 있습니다.
애플리케이션이 클라이언트 자격 증명 그랜트를 통해 토큰을 발급하려면, 먼저 클라이언트 자격 증명 그랜트용 클라이언트를 생성해야 합니다. passport:client Artisan 명령어에 --client 옵션을 사용하면 됩니다:
php artisan passport:client --client다음으로, Laravel\Passport\Http\Middleware\EnsureClientIsResourceOwner 미들웨어를 라우트에 연결합니다:
use Laravel\Passport\Http\Middleware\EnsureClientIsResourceOwner;
Route::get('/orders', function (Request $request) {
// 액세스 토큰이 유효하고, 클라이언트가 리소스 소유자인 경우...
})->middleware(EnsureClientIsResourceOwner::class);특정 스코프로 접근을 제한하고 싶다면, using 메서드에 필요한 스코프 목록을 전달하면 됩니다:
Route::get('/orders', function (Request $request) {
// 액세스 토큰이 유효하고, 클라이언트가 리소스 소유자이며, "servers:read"와 "servers:create" 스코프를 모두 가지고 있는 경우...
})->middleware(EnsureClientIsResourceOwner::using('servers:read', 'servers:create'));WARNING
Passport가 내부적으로 사용하는 OAuth2 서버는 클라이언트 자격 증명 토큰의 sub 클레임을 클라이언트의 식별자로 설정합니다. Passport는 기본적으로 클라이언트에 UUID를 사용하므로, 이 값이 사용자의 정수형 기본 키(primary key)와 충돌할 일은 없습니다. 하지만 Passport::$clientUuids를 false로 설정했다면, 클라이언트 자격 증명 토큰이 클라이언트 ID와 동일한 ID를 가진 사용자로 잘못 해석될 수 있습니다. 이런 경우, 이 미들웨어만으로는 전달된 토큰이 실제로 클라이언트 자격 증명 토큰임을 보장할 수 없습니다.
토큰 조회하기
이 그랜트 타입으로 토큰을 발급받으려면, oauth/token 엔드포인트에 요청을 보내면 됩니다:
use Illuminate\Support\Facades\Http;
$response = Http::asForm()->post('https://passport-app.test/oauth/token', [
'grant_type' => 'client_credentials',
'client_id' => 'your-client-id',
'client_secret' => 'your-client-secret',
'scope' => 'servers:read servers:create',
]);
return $response->json()['access_token'];Passport
개인 액세스 토큰
애플리케이션 사용자가 일반적인 인가 코드 리다이렉트 흐름을 거치지 않고, 자기 자신에게 직접 액세스 토큰을 발급하고 싶어하는 경우가 있습니다. 애플리케이션 UI를 통해 사용자가 스스로 토큰을 발급할 수 있도록 하면, 사용자가 API를 자유롭게 테스트해보거나 좀 더 간단한 방식으로 토큰을 발급받게 하고 싶을 때 유용합니다.
NOTE
애플리케이션에서 Passport를 주로 개인 액세스 토큰 발급 용도로만 사용하고 있다면, Laravel의 경량 API 토큰 발급 라이브러리인 Laravel Sanctum 사용을 고려해 보시기 바랍니다.
개인 액세스 클라이언트 생성하기
애플리케이션에서 개인 액세스 토큰을 발급하려면, 먼저 개인 액세스 클라이언트를 생성해야 합니다. --personal 옵션과 함께 passport:client Artisan 명령어를 실행하면 됩니다. 만약 이미 passport:install 명령어를 실행했다면, 이 단계는 별도로 실행할 필요가 없습니다:
php artisan passport:client --personal사용자 프로바이더 커스터마이징
애플리케이션에서 인증 사용자 프로바이더를 하나 이상 사용하고 있다면, artisan passport:client --personal 명령어 실행 시 --provider 옵션을 지정하여 개인 액세스 그랜트 클라이언트가 사용할 사용자 프로바이더를 명시할 수 있습니다. 여기서 지정하는 프로바이더 이름은 애플리케이션의 config/auth.php 설정 파일에 정의된 유효한 프로바이더와 일치해야 합니다. 이후 미들웨어를 이용해 라우트를 보호하여, 해당 가드에 지정된 프로바이더의 사용자만 인가되도록 제한할 수 있습니다.
개인 액세스 토큰 관리하기
개인 액세스 클라이언트를 생성한 후에는, App\Models\User 모델 인스턴스에서 createToken 메서드를 사용해 특정 사용자에게 토큰을 발급할 수 있습니다. createToken 메서드는 첫 번째 인수로 토큰의 이름을, 두 번째 인수로 (선택 사항인) 스코프 배열을 받습니다:
use App\Models\User;
use Illuminate\Support\Facades\Date;
use Laravel\Passport\Token;
$user = User::find($userId);
// 스코프 없이 토큰 생성하기...
$token = $user->createToken('My Token')->accessToken;
// 스코프를 지정하여 토큰 생성하기...
$token = $user->createToken('My Token', ['user:read', 'orders:create'])->accessToken;
// 모든 스코프를 부여하여 토큰 생성하기...
$token = $user->createToken('My Token', ['*'])->accessToken;
// 해당 사용자에게 속한, 유효한 모든 개인 액세스 토큰 조회하기...
$tokens = $user->tokens()
->with('client')
->where('revoked', false)
->where('expires_at', '>', Date::now())
->get()
->filter(fn (Token $token) => $token->client->hasGrantType('personal_access'));Passport
라우트 보호하기
미들웨어를 이용한 보호
Passport에는 요청에 담긴 액세스 토큰을 검증해주는 인증 가드가 포함되어 있습니다. api 가드가 passport 드라이버를 사용하도록 설정했다면, 유효한 액세스 토큰이 필요한 라우트에 auth:api 미들웨어만 지정해주면 됩니다.
Route::get('/user', function () {
// API로 인증된 사용자만 이 라우트에 접근할 수 있습니다...
})->middleware('auth:api');WARNING
클라이언트 자격 증명 그랜트(client credentials grant)를 사용하고 있다면, 라우트 보호에 auth:api 미들웨어 대신 Laravel\Passport\Http\Middleware\EnsureClientIsResourceOwner 미들웨어를 사용해야 합니다.
여러 개의 인증 가드 사용하기
애플리케이션에서 서로 다른 Eloquent 모델을 사용하는 여러 종류의 사용자를 인증해야 한다면, 사용자 프로바이더 유형별로 별도의 가드 설정을 정의해야 하는 경우가 많습니다. 이렇게 해두면 특정 사용자 프로바이더를 대상으로 하는 요청을 개별적으로 보호할 수 있습니다. 예를 들어, config/auth.php 설정 파일에 다음과 같이 가드를 구성했다고 가정해봅시다.
'guards' => [
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
'api-customers' => [
'driver' => 'passport',
'provider' => 'customers',
],
],아래 라우트는 customers 사용자 프로바이더를 사용하는 api-customers 가드로 들어오는 요청을 인증합니다.
Route::get('/customer', function () {
// ...
})->middleware('auth:api-customers');NOTE
Passport에서 여러 사용자 프로바이더를 함께 사용하는 방법에 대한 자세한 내용은 개인 액세스 토큰 문서와 패스워드 그랜트 문서를 참고하세요.
액세스 토큰 전달하기
Passport로 보호된 라우트를 호출할 때, API를 사용하는 클라이언트는 요청의 Authorization 헤더에 Bearer 토큰 형태로 액세스 토큰을 담아 전달해야 합니다. 예를 들어 Http 파사드를 사용한다면 다음과 같이 작성할 수 있습니다.
use Illuminate\Support\Facades\Http;
$response = Http::withHeaders([
'Accept' => 'application/json',
'Authorization' => "Bearer $accessToken",
])->get('https://passport-app.test/api/user');
return $response->json();토큰 스코프
스코프를 사용하면 API 클라이언트가 계정 접근 권한을 요청할 때 필요한 권한만 정확히 요청하도록 만들 수 있습니다. 예를 들어 이커머스 애플리케이션을 개발한다고 가정해봅시다. 모든 API 소비자에게 주문 생성 권한이 필요한 것은 아닐 수 있습니다. 어떤 경우에는 주문 배송 상태 조회 권한만 요청하도록 제한하고 싶을 수도 있죠. 즉, 스코프를 사용하면 사용자가 서드파티 애플리케이션이 자신을 대신해 수행할 수 있는 작업의 범위를 제한할 수 있습니다.
스코프 정의하기
API 스코프는 App\Providers\AppServiceProvider 클래스의 boot 메서드에서 Passport::tokensCan 메서드를 사용해 정의합니다. tokensCan 메서드는 스코프 이름과 스코프 설명으로 구성된 배열을 인자로 받습니다. 스코프 설명은 원하는 대로 자유롭게 작성할 수 있으며, 사용자에게 권한 승인 화면에 표시됩니다:
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Passport::tokensCan([
'user:read' => 'Retrieve the user info',
'orders:create' => 'Place orders',
'orders:read:status' => 'Check order status',
]);
}기본 스코프
클라이언트가 특정 스코프를 요청하지 않은 경우, defaultScopes 메서드를 사용해 Passport 서버가 토큰에 기본 스코프를 자동으로 부여하도록 설정할 수 있습니다. 일반적으로 이 메서드 역시 App\Providers\AppServiceProvider 클래스의 boot 메서드에서 호출합니다:
use Laravel\Passport\Passport;
Passport::tokensCan([
'user:read' => 'Retrieve the user info',
'orders:create' => 'Place orders',
'orders:read:status' => 'Check order status',
]);
Passport::defaultScopes([
'user:read',
'orders:create',
]);토큰에 스코프 할당하기
인가 코드를 요청할 때
인가 코드 그랜트를 사용해 액세스 토큰을 요청할 때, 소비자는 원하는 스코프를 scope 쿼리 문자열 파라미터로 지정해야 합니다. scope 파라미터는 공백으로 구분된 스코프 목록이어야 합니다:
Route::get('/redirect', function () {
$query = http_build_query([
'client_id' => 'your-client-id',
'redirect_uri' => 'https://third-party-app.com/callback',
'response_type' => 'code',
'scope' => 'user:read orders:create',
]);
return redirect('https://passport-app.test/oauth/authorize?'.$query);
});개인 액세스 토큰을 발급할 때
App\Models\User 모델의 createToken 메서드를 사용해 개인 액세스 토큰을 발급하는 경우, 원하는 스코프 배열을 두 번째 인자로 전달할 수 있습니다:
$token = $user->createToken('My Token', ['orders:create'])->accessToken;스코프 확인하기
Passport는 들어오는 요청이 특정 스코프가 부여된 토큰으로 인증되었는지 확인할 수 있는 두 가지 미들웨어를 제공합니다.
모든 스코프 확인하기
Laravel\Passport\Http\Middleware\CheckToken 미들웨어를 라우트에 지정하면, 들어오는 요청의 액세스 토큰이 나열된 스코프를 모두 가지고 있는지 검증할 수 있습니다:
use Laravel\Passport\Http\Middleware\CheckToken;
Route::get('/orders', function () {
// 액세스 토큰이 "orders:read"와 "orders:create" 스코프를 모두 가지고 있음...
})->middleware(['auth:api', CheckToken::using('orders:read', 'orders:create')]);스코프 중 하나라도 확인하기
Laravel\Passport\Http\Middleware\CheckTokenForAnyScope 미들웨어를 라우트에 지정하면, 들어오는 요청의 액세스 토큰이 나열된 스코프 중 적어도 하나를 가지고 있는지 검증할 수 있습니다:
use Laravel\Passport\Http\Middleware\CheckTokenForAnyScope;
Route::get('/orders', function () {
// 액세스 토큰이 "orders:read" 또는 "orders:create" 스코프 중 하나를 가지고 있음...
})->middleware(['auth:api', CheckTokenForAnyScope::using('orders:read', 'orders:create')]);스코프 속성(Attribute)
애플리케이션에서 컨트롤러 미들웨어 속성을 사용하고 있다면, Laravel\Passport\Attributes\AuthorizeToken 속성을 Passport의 스코프 미들웨어에 대한 편리한 단축 표현으로 사용할 수 있습니다:
<?php
namespace App\Http\Controllers;
use Laravel\Passport\Attributes\AuthorizeToken;
#[AuthorizeToken('orders:read')]
#[AuthorizeToken('orders:create', only: ['store'])]
class OrderController
{
#[AuthorizeToken(['orders:read', 'orders:create'], anyScope: true)]
public function index()
{
// 액세스 토큰이 "orders:read" 또는 "orders:create" 스코프 중 하나를 가지고 있음...
}
public function store()
{
// 액세스 토큰이 "orders:read"와 "orders:create" 스코프를 모두 가지고 있음...
}
}기본적으로 AuthorizeToken 속성은 지정된 스코프를 모두 요구합니다. anyScope: true를 전달하면 지정된 스코프 중 하나만 있어도 요청이 승인됩니다.
토큰 인스턴스에서 스코프 확인하기
액세스 토큰으로 인증된 요청이 애플리케이션에 도달한 이후에도, 인증된 App\Models\User 인스턴스의 tokenCan 메서드를 사용해 토큰이 특정 스코프를 가지고 있는지 확인할 수 있습니다:
use Illuminate\Http\Request;
Route::get('/orders', function (Request $request) {
if ($request->user()->tokenCan('orders:create')) {
// ...
}
});추가적인 스코프 관련 메서드
scopeIds 메서드는 정의된 모든 스코프의 ID / 이름을 배열로 반환합니다:
use Laravel\Passport\Passport;
Passport::scopeIds();scopes 메서드는 정의된 모든 스코프를 Laravel\Passport\Scope 인스턴스 배열로 반환합니다:
Passport::scopes();scopesFor 메서드는 주어진 ID / 이름과 일치하는 Laravel\Passport\Scope 인스턴스 배열을 반환합니다:
Passport::scopesFor(['user:read', 'orders:create']);주어진 스코프가 정의되어 있는지 여부는 hasScope 메서드로 확인할 수 있습니다:
Passport::hasScope('orders:create');Passport
SPA 인증
API를 개발하다 보면, 자바스크립트 애플리케이션에서 자신이 만든 API를 직접 소비해야 하는 경우가 자주 생깁니다. 이런 방식으로 API를 개발해두면, 외부에 공개하는 API와 동일한 API를 웹 애플리케이션, 모바일 애플리케이션, 서드파티 애플리케이션, 그리고 각종 패키지 매니저에 배포하는 SDK까지 모두 함께 사용할 수 있다는 장점이 있습니다.
일반적으로 자바스크립트 애플리케이션에서 API를 호출하려면, 액세스 토큰을 애플리케이션에 전달하고 매 요청마다 함께 보내야 합니다. 하지만 Passport에는 이 작업을 대신 처리해주는 미들웨어가 포함되어 있습니다. bootstrap/app.php 파일에서 web 미들웨어 그룹에 CreateFreshApiToken 미들웨어를 추가하기만 하면 됩니다.
use Laravel\Passport\Http\Middleware\CreateFreshApiToken;
->withMiddleware(function (Middleware $middleware): void {
$middleware->web(append: [
CreateFreshApiToken::class,
]);
})WARNING
CreateFreshApiToken 미들웨어는 반드시 미들웨어 스택의 가장 마지막에 위치해야 합니다.
이 미들웨어는 응답에 laravel_token 쿠키를 추가합니다. 이 쿠키에는 암호화된 JWT가 담겨 있으며, Passport는 이 JWT를 사용해 자바스크립트 애플리케이션에서 오는 API 요청을 인증합니다. JWT의 유효 기간은 session.lifetime 설정 값과 동일합니다. 브라우저는 이후의 모든 요청에 자동으로 이 쿠키를 함께 전송하므로, 액세스 토큰을 명시적으로 전달하지 않고도 애플리케이션의 API를 호출할 수 있습니다.
axios.get('/api/user')
.then(response => {
console.log(response.data);
});쿠키 이름 커스터마이징
필요하다면 Passport::cookie 메서드를 사용해 laravel_token 쿠키의 이름을 원하는 대로 변경할 수 있습니다. 보통 이 메서드는 애플리케이션의 App\Providers\AppServiceProvider 클래스에 있는 boot 메서드 안에서 호출합니다.
/**
* 애플리케이션 서비스를 부트스트랩합니다.
*/
public function boot(): void
{
Passport::cookie('custom_name');
}CSRF 보호
이 인증 방식을 사용할 때는 요청에 유효한 CSRF 토큰 헤더가 포함되어 있어야 합니다. Laravel의 기본 스켈레톤 애플리케이션과 모든 스타터 키트에 포함된 자바스크립트 스캐폴딩에는 Axios 인스턴스가 기본 설정되어 있어, 암호화된 XSRF-TOKEN 쿠키 값을 이용해 동일 출처(same-origin) 요청에 자동으로 X-XSRF-TOKEN 헤더를 실어 보냅니다.
NOTE
X-XSRF-TOKEN 대신 X-CSRF-TOKEN 헤더를 사용하고 싶다면, csrf_token() 함수가 제공하는 암호화되지 않은 토큰 값을 사용해야 합니다.
Passport
이벤트
Passport는 액세스 토큰과 리프레시 토큰을 발급할 때 이벤트를 발생시킵니다. 이 이벤트를 리스닝하여 데이터베이스에 저장된 다른 액세스 토큰을 정리하거나 폐기하는 등의 작업을 수행할 수 있습니다:
| 이벤트 이름 |
|---|
Laravel\Passport\Events\AccessTokenCreated |
Laravel\Passport\Events\AccessTokenRevoked |
Laravel\Passport\Events\RefreshTokenCreated |
NOTE
예를 들어 사용자가 새 액세스 토큰을 발급받을 때마다 이전에 발급된 오래된 토큰들을 폐기하고 싶다면, AccessTokenCreated 이벤트를 리스닝하는 리스너를 만들어 해당 사용자의 다른 토큰을 삭제하는 로직을 구현할 수 있습니다.
테스트
Passport의 actingAs 메서드를 사용하면 현재 인증된 사용자와 해당 사용자에게 부여할 스코프를 테스트 코드에서 손쉽게 지정할 수 있습니다. actingAs 메서드의 첫 번째 인자는 사용자 인스턴스이고, 두 번째 인자는 해당 사용자의 토큰에 부여할 스코프 배열입니다:
Pest
use App\Models\User;
use Laravel\Passport\Passport;
test('orders can be created', function () {
Passport::actingAs(
User::factory()->create(),
['orders:create']
);
$response = $this->post('/api/orders');
$response->assertStatus(201);
});PHPUnit
use App\Models\User;
use Laravel\Passport\Passport;
public function test_orders_can_be_created(): void
{
Passport::actingAs(
User::factory()->create(),
['orders:create']
);
$response = $this->post('/api/orders');
$response->assertStatus(201);
}마찬가지로, Passport의 actingAsClient 메서드를 사용하면 현재 인증된 클라이언트와 해당 클라이언트에게 부여할 스코프를 지정할 수 있습니다. actingAsClient 메서드의 첫 번째 인자는 클라이언트 인스턴스이고, 두 번째 인자는 해당 클라이언트의 토큰에 부여할 스코프 배열입니다:
Pest
use Laravel\Passport\Client;
use Laravel\Passport\Passport;
test('servers can be retrieved', function () {
Passport::actingAsClient(
Client::factory()->create(),
['servers:read']
);
$response = $this->get('/api/servers');
$response->assertStatus(200);
});PHPUnit
use Laravel\Passport\Client;
use Laravel\Passport\Passport;
public function test_servers_can_be_retrieved(): void
{
Passport::actingAsClient(
Client::factory()->create(),
['servers:read']
);
$response = $this->get('/api/servers');
$response->assertStatus(200);
}NOTE
actingAs와 actingAsClient는 실제 OAuth 인증 흐름을 거치지 않고도 인증된 사용자·클라이언트 상태를 즉시 만들어주는 테스트 전용 헬퍼입니다. 실제 토큰 발급 과정을 검증하려는 것이 아니라면, API 엔드포인트의 인가(authorization) 로직 자체를 테스트할 때 이 방식을 사용하는 것이 훨씬 간편합니다.