官方:https://www.howtographql.com/basics/0-introduction/
参考:
Laravel API 系列教程(四):基于 GraphQL 构建 Laravel API —— 基本使用篇
Laravel API 系列教程(五):基于 GraphQL 构建 Laravel API —— 高级使用篇
https://auth0.com/blog/developing-and-securing-graphql-apis-with-laravel/
D:laragonwwwgraphql>composer require rebing/graphql-laravel Using version ^5.1 for rebing/graphql-laravel ./composer.json has been updated Loading composer repositories with package information Updating dependencies (including require-dev) Package operations: 2 installs, 0 updates, 0 removals - Installing webonyx/graphql-php (v0.13.8): Downloading (100%) - Installing rebing/graphql-laravel (5.1.1): Downloading (100%) webonyx/graphql-php suggests installing react/promise (To leverage async resolving on React PHP platform) Writing lock file Generating optimized autoload files > IlluminateFoundationComposerScripts::postAutoloadDump > @php artisan package:discover --ansi Discovered Package: facade/ignition Discovered Package: fideloper/proxy Discovered Package: fruitcake/laravel-cors Discovered Package: laravel/tinker Discovered Package: laravel/ui Discovered Package: nesbot/carbon Discovered Package: nunomaduro/collision Discovered Package: rebing/graphql-laravel Discovered Package: tymon/jwt-auth Package manifest generated successfully. 3 packages you are using are looking for funding. Use the `composer fund` command to find out more! D:laragonwwwgraphql> D:laragonwwwgraphql>php artisan vendor:publish --provider="RebingGraphQLGraphQLServiceProvider" Copied File [vendor ebinggraphql-laravelconfigconfig.php] To [configgraphql.php] Publishing complete. D:laragonwwwgraphql>php artisan make:graphql:type UserType Type created successfully. D:laragonwwwgraphql>php artisan make:graphql:query UserQuery Query created successfully. D:laragonwwwgraphql>php artisan make:graphql:mutation UpdateUserPasswordMutation Mutation created successfully. D:laragonwwwgraphql>php artisan make:graphql:mutation UpdateUserEmailMutation Mutation created successfully. D:laragonwwwgraphql>php artisan make:graphql:type ArticleType Type created successfully. D:laragonwwwgraphql>php artisan make:graphql:enum ArticleStatusEnum Enum created successfully. D:laragonwwwgraphql>php artisan make:graphql:interface CharacterInterface Interface created successfully. D:laragonwwwgraphql>php artisan make:graphql:type HumanType Type created successfully. D:laragonwwwgraphql>php artisan make:graphql:field PictureField Field created successfully.
graphql.php
<?php declare(strict_types=1); use exampleMutationExampleMutation; use exampleQueryExampleQuery; use exampleTypeExampleRelationType; use exampleTypeExampleType; return [ // The prefix for routes 'prefix' => 'graphql', // The routes to make GraphQL request. Either a string that will apply // to both query and mutation or an array containing the key 'query' and/or // 'mutation' with the according Route // // Example: // // Same route for both query and mutation // // 'routes' => 'path/to/query/{graphql_schema?}', // // or define each route // // 'routes' => [ // 'query' => 'query/{graphql_schema?}', // 'mutation' => 'mutation/{graphql_schema?}', // ] // 'routes' => '{graphql_schema?}', // The controller to use in GraphQL request. Either a string that will apply // to both query and mutation or an array containing the key 'query' and/or // 'mutation' with the according Controller and method // // Example: // // 'controllers' => [ // 'query' => 'RebingGraphQLGraphQLController@query', // 'mutation' => 'RebingGraphQLGraphQLController@mutation' // ] // 'controllers' => RebingGraphQLGraphQLController::class . '@query', // Any middleware for the graphql route group 'middleware' => [], // Additional route group attributes // // Example: // // 'route_group_attributes' => ['guard' => 'api'] // 'route_group_attributes' => [], // The name of the default schema used when no argument is provided // to GraphQL::schema() or when the route is used without the graphql_schema // parameter. 'default_schema' => 'default', // The schemas for query and/or mutation. It expects an array of schemas to provide // both the 'query' fields and the 'mutation' fields. // // You can also provide a middleware that will only apply to the given schema // // Example: // // 'schema' => 'default', // // 'schemas' => [ // 'default' => [ // 'query' => [ // 'users' => 'AppGraphQLQueryUsersQuery' // ], // 'mutation' => [ // // ] // ], // 'user' => [ // 'query' => [ // 'profile' => 'AppGraphQLQueryProfileQuery' // ], // 'mutation' => [ // // ], // 'middleware' => ['auth'], // ], // 'user/me' => [ // 'query' => [ // 'profile' => 'AppGraphQLQueryMyProfileQuery' // ], // 'mutation' => [ // // ], // 'middleware' => ['auth'], // ], // ] // 'schemas' => [ 'default' => [ 'query' => [ // 'example_query' => ExampleQuery::class, 'users' => AppGraphQLQueriesUserQuery::class, ], 'mutation' => [ // 'example_mutation' => ExampleMutation::class, 'updateUserPassword' => AppGraphQLMutationsUpdateUserPasswordMutation::class, 'updateUserEmail' => AppGraphQLMutationsUpdateUserEmailMutation::class, ], 'middleware' => ['auth:api'], 'method' => ['get', 'post'], ], ], // The types available in the application. You can then access it from the // facade like this: GraphQL::type('user') // // Example: // // 'types' => [ // 'user' => 'AppGraphQLTypeUserType' // ] // 'types' => [ // 'example' => ExampleType::class, // 'relation_example' => ExampleRelationType::class, // RebingGraphQLSupportUploadType::class, 'User' => AppGraphQLTypesUserType::class, 'Article' => AppGraphQLTypesArticleType::class, 'ArticleStatusEnum' => AppGraphQLEnumsArticleStatusEnum::class, ], // The types will be loaded on demand. Default is to load all types on each request // Can increase performance on schemes with many types // Presupposes the config type key to match the type class name property 'lazyload_types' => false, // This callable will be passed the Error object for each errors GraphQL catch. // The method should return an array representing the error. // Typically: // [ // 'message' => '', // 'locations' => [] // ] 'error_formatter' => ['RebingGraphQLGraphQL', 'formatError'], /* * Custom Error Handling * * Expected handler signature is: function (array $errors, callable $formatter): array * * The default handler will pass exceptions to laravel Error Handling mechanism */ 'errors_handler' => ['RebingGraphQLGraphQL', 'handleErrors'], // You can set the key, which will be used to retrieve the dynamic variables 'params_key' => 'variables', /* * Options to limit the query complexity and depth. See the doc * @ https://webonyx.github.io/graphql-php/security * for details. Disabled by default. */ 'security' => [ 'query_max_complexity' => null, 'query_max_depth' => null, 'disable_introspection' => false, ], /* * You can define your own pagination type. * Reference RebingGraphQLSupportPaginationType::class */ 'pagination_type' => RebingGraphQLSupportPaginationType::class, /* * Config for GraphiQL (see (https://github.com/graphql/graphiql). */ 'graphiql' => [ 'prefix' => '/graphiql', 'controller' => RebingGraphQLGraphQLController::class . '@graphiql', 'middleware' => [], 'view' => 'graphql::graphiql', 'display' => env('ENABLE_GRAPHIQL', true), ], /* * Overrides the default field resolver * See http://webonyx.github.io/graphql-php/data-fetching/#default-field-resolver * * Example: * * ```php * 'defaultFieldResolver' => function ($root, $args, $context, $info) { * }, * ``` * or * ```php * 'defaultFieldResolver' => [SomeKlass::class, 'someMethod'], * ``` */ 'defaultFieldResolver' => null, /* * Any headers that will be added to the response returned by the default controller */ 'headers' => [], /* * Any JSON encoding options when returning a response from the default controller * See http://php.net/manual/function.json-encode.php for the full list of options */ 'json_encoding_options' => 0, ];
User.php:
<?php namespace App; use IlluminateContractsAuthMustVerifyEmail; use IlluminateDatabaseEloquentSoftDeletes; use IlluminateFoundationAuthUser as Authenticatable; use IlluminateNotificationsNotifiable; use TymonJWTAuthContractsJWTSubject; class User extends Authenticatable implements JWTSubject { use Notifiable, SoftDeletes; /** * 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', ]; public function articles() { return $this->hasMany(Article::class); } public function getJWTIdentifier() { return $this->getKey(); } public function getJWTCustomClaims() { return []; } }
Article.php:
<?php namespace App; use IlluminateDatabaseEloquentModel; use IlluminateDatabaseEloquentSoftDeletes; class Article extends Model { use SoftDeletes; protected $fillable = ['user_id', 'title', 'body']; public function user() { return $this->belongsTo(User::class); } }
ArticleController.php:
<?php namespace AppHttpControllers; use AppArticle; use IlluminateHttpRequest; use IlluminateSupportFacadesValidator; class ArticleController extends Controller { public function __construct() { // $this->middleware('auth:api')->except(['show', 'index']); $this->middleware('auth:api')->except(['show']); } /** * Display a listing of the resource. * * *///@return IlluminateHttpResponse public function index(Request $request) { $user = $request->user(); $articles = $user->articles()->get(); return response()->json($articles); } /** * Store a newly created resource in storage. * * @param IlluminateHttpRequest $request * *///@return IlluminateHttpResponse public function store(Request $request) { $rules = [ 'title' => 'required|string|unique:articles|max:255', 'body' => 'required|string|max:255', ]; $message = [ 'title.required' => '必须输入title', 'title.string' => 'title格式为字符串', 'title.max' => 'title不要超过255', 'title.unique' => 'title不可重复', 'body.required' => '必须输入title', 'body.string' => 'title格式为字符串', 'body.max' => 'title不要超过255', ]; $validator = Validator::make($request->all(), $rules, $message); if ($validator->fails()) { return response()->json($validator->errors()->getMessages(), 302); } $user = $request->user(); if (!$user) { return response()->json([ 'error' => '请先登录', ], 401); } $data = $validator->validated(); $data['user_id'] = $user->id; $article = Article::create($data); return response()->json($article, 201); } /** * Display the specified resource. * * @param AppArticle $article * */ //@return IlluminateHttpResponse public function show(Article $article) { return response()->json($article); } /** * Update the specified resource in storage. * * @param IlluminateHttpRequest $request * @param AppArticle $article * */ // @return IlluminateHttpResponse public function update(Request $request, Article $article) { if ($article->user->id !== auth()->id()) { return response()->json([ 'error' => '没有权限操作不属于自己的文章', ], 402); } if ($request->get('title') === $article->title) { $rules = [ 'title' => 'required|string|max:255', 'body' => 'required|string|max:255', ]; } else { $rules = [ 'title' => 'required|string|unique:articles|max:255', 'body' => 'required|string|max:255', ]; } $message = [ 'title.required' => '必须输入title', 'title.string' => 'title格式为字符串', 'title.max' => 'title不要超过255', 'title.unique' => 'title不可重复', 'body.required' => '必须输入title', 'body.string' => 'title格式为字符串', 'body.max' => 'title不要超过255', ]; $validator = Validator::make($request->all(), $rules, $message); if ($validator->fails()) { return response()->json($validator->errors()->getMessages(), 302); } $data = $validator->validated(); if (!$article->update($data)) { return response()->json([ 'error' => 'Article update failed', ]); } return response()->json([ 'message' => 'Article updated', ], 200); } /** * Remove the specified resource from storage. * * @param AppArticle $article * */ //@return IlluminateHttpResponse public function destroy(Article $article) { $article->delete(); return response()->json([ 'message' => 'Successfully Deleted', ], 200); } }
ApiAuthAuthController.php
<?php namespace AppHttpControllersApiAuth; use AppHttpControllersController; use AppUser; use IlluminateHttpRequest; use IlluminateSupportFacadesHash; use IlluminateSupportFacadesValidator; class AuthController extends Controller { public function __construct() { $this->middleware('auth:api')->except(['login', 'register']); } public function login(Request $request) { $credentials = $request->validate([ 'email' => 'required|string|email', 'password' => 'required|string', ]); if (!$token = auth('api')->attempt($credentials)) { return response()->json([ 'error' => 'Wrong credentials', ], 401); } return $this->responseWithToken($token); } public function logout() { auth()->logout(); return response()->json([ 'message' => 'Successfully Logged out', ], 200); } public function refresh() { return $this->responseWithToken(auth()->refresh()); } public function me() { $user = auth('api')->user(); return response()->json([ 'user' => $user, 'token' => [ 'access_token' => auth('api')->login($user), 'token_type' => 'bearer', 'expires_in' => auth('api')->factory()->getTTL() * 60, ] ]); } public function register(Request $request) { $rules = [ 'name' => 'required|string|max:255', 'email' => 'required|string|email|unique:users|max:255', 'password' => 'required|string|confirmed|min:6|max:255', ]; $validator = Validator::make($request->all(), $rules); if ($validator->fails()) { return response()->json($validator->errors()->getMessages(), 302); } $data = $validator->validated(); $user = User::create( [ 'name' => $data['name'], 'email' => $data['email'], 'password' => Hash::make($data['password']), ] ); $token = auth('api')->login($user); return $this->responseWithToken($token); } public function responseWithToken($token) { return response()->json([ 'access_token' => $token, 'token_type' => 'bearer', 'expires_in' => auth('api')->factory()->getTTL() * 60, ]); } }
GraphQLTypesUserType.php:
<?php declare(strict_types=1); namespace AppGraphQLTypes; use AppGraphQLFieldsPictureField; use GraphQLGraphQL; use GraphQLTypeDefinitionType; use RebingGraphQLSupportType as GraphQLType; class UserType extends GraphQLType { protected $attributes = [ 'name' => 'User', 'description' => 'A type' ]; public function fields(): array { return [ 'id' => [ 'type' => Type::nonNull(Type::string()), 'description' => 'The id of the user', ], 'email' => [ 'type' => Type::string(), 'description' => 'The email of the user', ], 'articles' => [ 'type' => Type::listOf(RebingGraphQLSupportFacadesGraphQL::type('Article')), 'description' => 'The articles of the user', ], 'picture' => PictureField::class ]; } protected function resolveEmailField($root, $args) { return strtolower($root->email); } protected function resolveArticlesField($root, $args) { if (isset($args['id'])) { return $root->articles->where('id', $args['id']); } return $root->articles; } }
GraphQLTypesArticleType.php:
<?php declare(strict_types=1); namespace AppGraphQLTypes; use GraphQLTypeDefinitionType; use RebingGraphQLSupportFacadesGraphQL; use RebingGraphQLSupportType as GraphQLType; class ArticleType extends GraphQLType { protected $attributes = [ 'name' => 'Article', 'description' => 'A type' ]; public function fields(): array { return [ 'id' => [ 'name' => 'id', 'type' => Type::nonNull(Type::string()), 'description' => 'The id of the article', ], 'title' => [ 'name' => 'title', 'type' => Type::nonNull(Type::string()), 'description' => 'The title of the article', ], 'body' => [ 'name' => 'body', 'type' => Type::nonNull(Type::string()), 'description' => 'The body of the article', ], 'status' => [ 'name' => 'status', 'type' => GraphQL::type('ArticleStatusEnum'), 'description' => 'The status of the article', ] ]; } }
GraphQLTypesHumanType.php:
<?php declare(strict_types=1); namespace AppGraphQLTypes; use GraphQLTypeDefinitionType; use RebingGraphQLSupportFacadesGraphQL; use RebingGraphQLSupportType as GraphQLType; class HumanType extends GraphQLType { protected $attributes = [ 'name' => 'Human', 'description' => 'A Human' ]; public function fields(): array { return [ 'id' => [ 'type' => Type::nonNull(Type::int()), 'description' => 'The id of the human.', ], 'appearsIn' => [ 'type' => Type::nonNull(Type::listOf(GraphQL::type('Episode'))), 'description' => 'A list of episodes in which the human has an appearance.' ], 'totalCredits' => [ 'type' => Type::nonNull(Type::int()), 'description' => 'The total amount of credits this human owns.' ] ]; } public function interfaces(): array { return [ GraphQL::type('Character') ]; } }
GraphQLQueriesUserQuery.php:
<?php declare(strict_types=1); namespace AppGraphQLQueries; use AppUser; use Closure; use GraphQLTypeDefinitionResolveInfo; use GraphQLTypeDefinitionType; use IlluminateSupportFacadesAuth; use RebingGraphQLSupportFacadesGraphQL; use RebingGraphQLSupportQuery; use RebingGraphQLSupportSelectFields; class UserQuery extends Query { protected $attributes = [ 'name' => 'user', 'description' => 'A query' ]; public function type(): Type { return Type::listOf(GraphQL::type('User')); } public function args(): array { return [ 'id' => [ 'name' => 'id', 'type' => Type::string(), ], 'email' => [ 'name' => 'email', 'type' => Type::string(), ], ]; } // public function resolve($root, $args, $context, ResolveInfo $resolveInfo, Closure $getSelectFields) // { //// /** @var SelectFields $fields */ //// $fields = $getSelectFields(); //// $select = $fields->getSelect(); //// $with = $fields->getRelations(); //// //// return [ //// 'The user works', //// ]; // if (isset($args['id'])) { // return User::where('id', $args['id'])->get(); // } elseif (isset($args['email'])) { // return User::where('email', $args['email'])->get(); // } else { // return User::all(); // } // } public function resolve($root, $args, $context, ResolveInfo $resolveInfo, Closure $getSelectFields) { $fields = $resolveInfo->getFieldSelection($depth = 3); if (isset($args['id'])) { $user = User::where('id', $args['id']); } elseif (isset($args['email'])) { $user = User::where('email', $args['email']); } else { $user = User::query(); } foreach ($fields as $field => $keys) { if ($field == 'articles') { $user->with('articles'); } } return $user->get(); } }
GraphQLMutationsUpdateUserPasswordMutation.php:
<?php declare(strict_types=1); namespace AppGraphQLMutations; use AppUser; use Closure; use GraphQLTypeDefinitionResolveInfo; use GraphQLTypeDefinitionType; use IlluminateSupportFacadesAuth; use IlluminateSupportFacadesHash; use RebingGraphQLSupportFacadesGraphQL; use RebingGraphQLSupportMutation; use RebingGraphQLSupportSelectFields; use TymonJWTAuthJWTAuth; class UpdateUserPasswordMutation extends Mutation { protected $attributes = [ 'name' => 'updateUserPassword', 'description' => 'A mutation' ]; public function type(): Type { return GraphQL::type('User'); } public function args(): array { return [ 'id' => [ 'name' => 'id', 'type' => Type::nonNull(Type::string()), ], 'password' => [ 'name' => 'password', 'type' => Type::nonNull(Type::string()), ], ]; } public function resolve($root, $args, $context, ResolveInfo $resolveInfo, Closure $getSelectFields) { // $fields = $getSelectFields(); // $select = $fields->getSelect(); // $with = $fields->getRelations(); // // return []; $user = User::find($args['id']); if (!$user) { return null; } $user->password = Hash::make($args['password']); $user->save(); return $user; } }
GraphQLMutationsUpdateUserEmailMutation.php:
<?php declare(strict_types=1); namespace AppGraphQLMutations; use Closure; use GraphQLTypeDefinitionResolveInfo; use GraphQLTypeDefinitionType; use AppUser; use RebingGraphQLSupportFacadesGraphQL; use RebingGraphQLSupportMutation; use RebingGraphQLSupportSelectFields; class UpdateUserEmailMutation extends Mutation { protected $attributes = [ 'name' => 'updateUserEmail', 'description' => 'A mutation' ]; public function type(): Type { return GraphQL::type('User'); } public function args(): array { return [ 'id' => [ 'name' => 'id', 'type' => Type::nonNull(Type::string()), ], 'email' => [ 'name' => 'email', 'type' => Type::nonNull(Type::string()), ], ]; } public function rules(array $args = []): array { return [ 'id' => 'required', 'email' => ['required', 'email'], ]; } public function resolve($root, $args, $context, ResolveInfo $resolveInfo, Closure $getSelectFields) { // $fields = $getSelectFields(); // $select = $fields->getSelect(); // $with = $fields->getRelations(); // // return []; $user = User::find($args['id']); if (!$user) { return null; } $user->email = $args['email']; $user->save(); return $user; } }
GraphQLInterfacesCharacterInterface.php:
<?php declare(strict_types=1); namespace AppGraphQLInterfaces; use GraphQLTypeDefinitionStringType; use GraphQLTypeDefinitionType; use RebingGraphQLSupportFacadesGraphQL; use RebingGraphQLSupportInterfaceType; class CharacterInterface extends InterfaceType { protected $attributes = [ 'name' => 'CharacterInterface', 'description' => 'Character interface', ]; public function resolveType($root): StringType { // Use the resolveType to resolve the Type which is implemented trough this interface $type = $root['type']; if ($type === 'human') { return GraphQL::type('Human'); } else if ($type === 'droid') { return GraphQL::type('Droid'); } } public function fields(): array { return [ 'id' => [ 'type' => Type::nonNull(Type::int()), 'description' => 'The id of the character.' ], 'appearsIn' => [ 'type' => Type::nonNull(Type::listOf(GraphQL::type('Episode'))), 'description' => 'A list of episodes in which the character has an appearance.' ], ]; } }
GraphQLFieldsPictureField.php:
<?php declare(strict_types=1); namespace AppGraphQLFields; use GraphQLTypeDefinitionType; use RebingGraphQLSupportField; class PictureField extends Field { protected $attributes = [ 'description' => 'A picture' ]; public function type(): Type { return Type::string(); } public function args(): array { return [ 'width' => [ 'type' => Type::int(), 'description' => 'The width of the picture' ], 'height' => [ 'type' => Type::int(), 'description' => 'The height of the picture' ] ]; } public function resolve($root, $args): string { $width = isset($args['width']) ? $args['width'] : 100; $height = isset($args['height']) ? $args['height'] : 100; return 'http://placehold.it/' . $width . 'x' . $height; } }
GraphQLEnumsArticleStatusEnum.php:
<?php declare(strict_types=1); namespace AppGraphQLEnums; use RebingGraphQLSupportEnumType; class ArticleStatusEnum extends EnumType { protected $enumObject = true; protected $attributes = [ 'name' => 'ArticleStatusEnum', 'description' => 'Article Status Enum', // 'values' => [ //// 'TEST' => [ //// 'value' => 1, //// 'description' => 'test', //// ], // 'APPROVED' => [ // 'value' => 1, // 'description' => 'approved', // ], // 'REJECT' => [ // 'value' => 0, // 'description' => 'reject', // ] // ], ]; public function values() { return [ 'APPROVED' => '1', 'REJECT' => '0' ]; } }
appExceptionsHandler.php:
<?php namespace AppExceptions; use IlluminateAuthAuthenticationException; use IlluminateFoundationExceptionsHandler as ExceptionHandler; use Throwable; class Handler extends ExceptionHandler { /** * A list of the exception types that are not reported. * * @var array */ protected $dontReport = [ // ]; /** * A list of the inputs that are never flashed for validation exceptions. * * @var array */ protected $dontFlash = [ 'password', 'password_confirmation', ]; /** * Report or log an exception. * * @param Throwable $exception * @return void * * @throws Exception */ public function report(Throwable $exception) { parent::report($exception); } /** * Render an exception into an HTTP response. * * @param IlluminateHttpRequest $request * @param Throwable $exception * @return SymfonyComponentHttpFoundationResponse * * @throws Throwable */ public function render($request, Throwable $exception) { if ($exception instanceof AuthenticationException) { return response()->json( ['error' => '请登录'], 401 ); } return parent::render($request, $exception); } }
configauth.php:
<?php return [ /* |-------------------------------------------------------------------------- | Authentication Defaults |-------------------------------------------------------------------------- | | This option controls the default authentication "guard" and password | reset options for your application. You may change these defaults | as required, but they're a perfect start for most applications. | */ 'defaults' => [ 'guard' => 'web', 'passwords' => 'users', ], /* |-------------------------------------------------------------------------- | Authentication Guards |-------------------------------------------------------------------------- | | Next, you may define every authentication guard for your application. | Of course, a great default configuration has been defined for you | here which uses session storage and the Eloquent user provider. | | All authentication drivers have a user provider. This defines how the | users are actually retrieved out of your database or other storage | mechanisms used by this application to persist your user's data. | | Supported: "session", "token" | */ 'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'api' => [ 'driver' => 'jwt', 'provider' => 'users', 'hash' => false, ], ], /* |-------------------------------------------------------------------------- | User Providers |-------------------------------------------------------------------------- | | All authentication drivers have a user provider. This defines how the | users are actually retrieved out of your database or other storage | mechanisms used by this application to persist your user's data. | | If you have multiple user tables or models you may configure multiple | sources which represent each model / table. These sources may then | be assigned to any extra authentication guards you have defined. | | Supported: "database", "eloquent" | */ 'providers' => [ 'users' => [ 'driver' => 'eloquent', 'model' => AppUser::class, ], // 'users' => [ // 'driver' => 'database', // 'table' => 'users', // ], ], /* |-------------------------------------------------------------------------- | Resetting Passwords |-------------------------------------------------------------------------- | | You may specify multiple password reset configurations if you have more | than one user table or model in the application and you want to have | separate password reset settings based on the specific user types. | | The expire time is the number of minutes that the reset token should be | considered valid. This security feature keeps tokens short-lived so | they have less time to be guessed. You may change this as needed. | */ 'passwords' => [ 'users' => [ 'provider' => 'users', 'table' => 'password_resets', 'expire' => 60, 'throttle' => 60, ], ], /* |-------------------------------------------------------------------------- | Password Confirmation Timeout |-------------------------------------------------------------------------- | | Here you may define the amount of seconds before a password confirmation | times out and the user is prompted to re-enter their password via the | confirmation screen. By default, the timeout lasts for three hours. | */ 'password_timeout' => 10800, ];
configjwt.php:
<?php /* * This file is part of jwt-auth. * * (c) Sean Tymon <tymon148@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ return [ /* |-------------------------------------------------------------------------- | JWT Authentication Secret |-------------------------------------------------------------------------- | | Don't forget to set this in your .env file, as it will be used to sign | your tokens. A helper command is provided for this: | `php artisan jwt:secret` | | Note: This will be used for Symmetric algorithms only (HMAC), | since RSA and ECDSA use a private/public key combo (See below). | */ 'secret' => env('JWT_SECRET'), /* |-------------------------------------------------------------------------- | JWT Authentication Keys |-------------------------------------------------------------------------- | | The algorithm you are using, will determine whether your tokens are | signed with a random string (defined in `JWT_SECRET`) or using the | following public & private keys. | | Symmetric Algorithms: | HS256, HS384 & HS512 will use `JWT_SECRET`. | | Asymmetric Algorithms: | RS256, RS384 & RS512 / ES256, ES384 & ES512 will use the keys below. | */ 'keys' => [ /* |-------------------------------------------------------------------------- | Public Key |-------------------------------------------------------------------------- | | A path or resource to your public key. | | E.g. 'file://path/to/public/key' | */ 'public' => env('JWT_PUBLIC_KEY'), /* |-------------------------------------------------------------------------- | Private Key |-------------------------------------------------------------------------- | | A path or resource to your private key. | | E.g. 'file://path/to/private/key' | */ 'private' => env('JWT_PRIVATE_KEY'), /* |-------------------------------------------------------------------------- | Passphrase |-------------------------------------------------------------------------- | | The passphrase for your private key. Can be null if none set. | */ 'passphrase' => env('JWT_PASSPHRASE'), ], /* |-------------------------------------------------------------------------- | JWT time to live |-------------------------------------------------------------------------- | | Specify the length of time (in minutes) that the token will be valid for. | Defaults to 1 hour. | | You can also set this to null, to yield a never expiring token. | Some people may want this behaviour for e.g. a mobile app. | This is not particularly recommended, so make sure you have appropriate | systems in place to revoke the token if necessary. | Notice: If you set this to null you should remove 'exp' element from 'required_claims' list. | */ 'ttl' => env('JWT_TTL', 60), /* |-------------------------------------------------------------------------- | Refresh time to live |-------------------------------------------------------------------------- | | Specify the length of time (in minutes) that the token can be refreshed | within. I.E. The user can refresh their token within a 2 week window of | the original token being created until they must re-authenticate. | Defaults to 2 weeks. | | You can also set this to null, to yield an infinite refresh time. | Some may want this instead of never expiring tokens for e.g. a mobile app. | This is not particularly recommended, so make sure you have appropriate | systems in place to revoke the token if necessary. | */ 'refresh_ttl' => env('JWT_REFRESH_TTL', 20160), /* |-------------------------------------------------------------------------- | JWT hashing algorithm |-------------------------------------------------------------------------- | | Specify the hashing algorithm that will be used to sign the token. | | See here: https://github.com/namshi/jose/tree/master/src/Namshi/JOSE/Signer/OpenSSL | for possible values. | */ 'algo' => env('JWT_ALGO', 'HS256'), /* |-------------------------------------------------------------------------- | Required Claims |-------------------------------------------------------------------------- | | Specify the required claims that must exist in any token. | A TokenInvalidException will be thrown if any of these claims are not | present in the payload. | */ 'required_claims' => [ 'iss', 'iat', 'exp', 'nbf', 'sub', 'jti', ], /* |-------------------------------------------------------------------------- | Persistent Claims |-------------------------------------------------------------------------- | | Specify the claim keys to be persisted when refreshing a token. | `sub` and `iat` will automatically be persisted, in | addition to the these claims. | | Note: If a claim does not exist then it will be ignored. | */ 'persistent_claims' => [ // 'foo', // 'bar', ], /* |-------------------------------------------------------------------------- | Lock Subject |-------------------------------------------------------------------------- | | This will determine whether a `prv` claim is automatically added to | the token. The purpose of this is to ensure that if you have multiple | authentication models e.g. `AppUser` & `AppOtherPerson`, then we | should prevent one authentication request from impersonating another, | if 2 tokens happen to have the same id across the 2 different models. | | Under specific circumstances, you may want to disable this behaviour | e.g. if you only have one authentication model, then you would save | a little on token size. | */ 'lock_subject' => true, /* |-------------------------------------------------------------------------- | Leeway |-------------------------------------------------------------------------- | | This property gives the jwt timestamp claims some "leeway". | Meaning that if you have any unavoidable slight clock skew on | any of your servers then this will afford you some level of cushioning. | | This applies to the claims `iat`, `nbf` and `exp`. | | Specify in seconds - only if you know you need it. | */ 'leeway' => env('JWT_LEEWAY', 0), /* |-------------------------------------------------------------------------- | Blacklist Enabled |-------------------------------------------------------------------------- | | In order to invalidate tokens, you must have the blacklist enabled. | If you do not want or need this functionality, then set this to false. | */ 'blacklist_enabled' => env('JWT_BLACKLIST_ENABLED', true), /* | ------------------------------------------------------------------------- | Blacklist Grace Period | ------------------------------------------------------------------------- | | When multiple concurrent requests are made with the same JWT, | it is possible that some of them fail, due to token regeneration | on every request. | | Set grace period in seconds to prevent parallel request failure. | */ 'blacklist_grace_period' => env('JWT_BLACKLIST_GRACE_PERIOD', 0), /* |-------------------------------------------------------------------------- | Cookies encryption |-------------------------------------------------------------------------- | | By default Laravel encrypt cookies for security reason. | If you decide to not decrypt cookies, you will have to configure Laravel | to not encrypt your cookie token by adding its name into the $except | array available in the middleware "EncryptCookies" provided by Laravel. | see https://laravel.com/docs/master/responses#cookies-and-encryption | for details. | | Set it to true if you want to decrypt cookies. | */ 'decrypt_cookies' => false, /* |-------------------------------------------------------------------------- | Providers |-------------------------------------------------------------------------- | | Specify the various providers used throughout the package. | */ 'providers' => [ /* |-------------------------------------------------------------------------- | JWT Provider |-------------------------------------------------------------------------- | | Specify the provider that is used to create and decode the tokens. | */ 'jwt' => TymonJWTAuthProvidersJWTLcobucci::class, /* |-------------------------------------------------------------------------- | Authentication Provider |-------------------------------------------------------------------------- | | Specify the provider that is used to authenticate users. | */ 'auth' => TymonJWTAuthProvidersAuthIlluminate::class, /* |-------------------------------------------------------------------------- | Storage Provider |-------------------------------------------------------------------------- | | Specify the provider that is used to store tokens in the blacklist. | */ 'storage' => TymonJWTAuthProvidersStorageIlluminate::class, ], ];
api.php:
<?php use IlluminateHttpRequest; use IlluminateSupportFacadesRoute; /* |-------------------------------------------------------------------------- | API Routes |-------------------------------------------------------------------------- |php | Here is where you can register API routes for your application. These | routes are loaded by the RouteServiceProvider within a group which | is assigned the "api" middleware group. Enjoy building your API! | */ Route::middleware('auth:api')->get('/user', function (Request $request) { return $request->user(); }); Route::apiResource('articles', 'ArticleController'); Route::post('login', 'ApiAuthAuthController@login')->middleware('guest')->name('login.api'); Route::post('logout', 'ApiAuthAuthController@logout')->middleware('auth:api')->name('logout.api'); Route::post('refresh', 'ApiAuthAuthController@refresh')->middleware('auth:api')->name('refresh.api'); Route::post('register', 'ApiAuthAuthController@register')->middleware('guest')->name('register.api'); Route::get('me', 'ApiAuthAuthController@me')->middleware('auth:api');
.env:
APP_NAME=Laravel APP_ENV=local APP_KEY=base64:UkfFliQ/y1iTF4uW8QVQar7t855lRmD5Ap3i8rySShw= APP_DEBUG=true APP_URL=http://localhost LOG_CHANNEL=stack DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=graphql DB_USERNAME=root DB_PASSWORD= BROADCAST_DRIVER=log CACHE_DRIVER=file QUEUE_CONNECTION=sync SESSION_DRIVER=file SESSION_LIFETIME=120 REDIS_HOST=127.0.0.1 REDIS_PASSWORD=null REDIS_PORT=6379 MAIL_MAILER=smtp MAIL_HOST=smtp.mailtrap.io MAIL_PORT=2525 MAIL_USERNAME=null MAIL_PASSWORD=null MAIL_ENCRYPTION=null MAIL_FROM_ADDRESS=null MAIL_FROM_NAME="${APP_NAME}" AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= AWS_DEFAULT_REGION=us-east-1 AWS_BUCKET= PUSHER_APP_ID= PUSHER_APP_KEY= PUSHER_APP_SECRET= PUSHER_APP_CLUSTER=mt1 MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" JWT_SECRET=1Je1e5gh0ZxO5XJ7ScOJJZq6WAbX0Uez7yP5Vkvf1tyyUkioTziZ0aFpZ1tDx9KP
CreateUsersTable:
<?php use IlluminateDatabaseMigrationsMigration; use IlluminateDatabaseSchemaBlueprint; use IlluminateSupportFacadesSchema; class CreateUsersTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('users', function (Blueprint $table) { $table->id(); $table->string('name'); $table->string('email')->unique(); $table->timestamp('email_verified_at')->nullable(); $table->string('password'); $table->softDeletes(); $table->rememberToken(); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('users'); } }
CreateArticlesTable
<?php use IlluminateDatabaseMigrationsMigration; use IlluminateDatabaseSchemaBlueprint; use IlluminateSupportFacadesSchema; class CreateArticlesTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('articles', function (Blueprint $table) { $table->id(); $table->string('title'); $table->text('body'); $table->unsignedBigInteger('user_id'); $table->softDeletes(); $table->timestamps(); }); Schema::table('articles', function (Blueprint $table) { $table->foreign('user_id') ->references('id') ->on('users') ->cascadeOnDelete(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('articles'); } }
ArticleFactory.php:
<?php /** @var IlluminateDatabaseEloquentFactory $factory */ use AppArticle; use FakerGenerator as Faker; $factory->define(Article::class, function (Faker $faker) { return [ // 'title' => $faker->sentence, 'body' => $faker->paragraph, ]; });
ArticleTableSeeder.php:
<?php use IlluminateDatabaseSeeder; class ArticleTableSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { // factory(AppArticle::class, 50)->create( ['user_id' => 1] ); } }
DatabaseSeeder.php:
<?php use IlluminateDatabaseSeeder; class DatabaseSeeder extends Seeder { /** * Seed the application's database. * * @return void */ public function run() { // $this->call(UserSeeder::class); $this->call(UserTableSeeder::class); $this->call(ArticleTableSeeder::class); } }
UserTableSeeder.php:
<?php use AppUser; use IlluminateDatabaseSeeder; class UserTableSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { // factory(User::class, 10)->create(); } }
Github: