Skip to content

Commit 2f2435b

Browse files
authored
Merge pull request #787 from teohhanhui/improve-jwt
Improve JWT docs
2 parents f0d86ab + 11712f3 commit 2f2435b

File tree

4 files changed

+119
-40
lines changed

4 files changed

+119
-40
lines changed

admin/authentication-support.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
# Authentication Support
22

33
Authentication can easily be handled when using the API Platform's admin library.
4-
In the following section, we will assume [the API is secured using JWT](https://api-platform.com/docs/core/jwt), but the
5-
process is similar for other authentication mechanisms. The `login_uri` is the full URI to the route specified by the `firewalls.login.json_login.check_path` config in the [JWT documentation](https://api-platform.com/docs/core/jwt).
4+
In the following section, we will assume [the API is secured using JWT](../core/jwt.md), but the
5+
process is similar for other authentication mechanisms. The `authenticationTokenUri` is the full URI to the path / route specified by the `firewalls.{name}.json_login.check_path` config in the [JWT documentation](../core/jwt.md).
66

77
The first step is to create a client to handle the authentication process:
88

99
```javascript
10-
// src/authProvider.js
10+
// admin/src/authProvider.js
1111
import { AUTH_LOGIN, AUTH_LOGOUT, AUTH_ERROR, AUTH_CHECK } from 'react-admin';
1212

13-
// Change this to be your own login check route.
14-
const login_uri = 'https://demo.api-platform.com/login_check';
13+
// Change this to be your own authentication token URI.
14+
const authenticationTokenUri = 'https://localhost:8443/authentication_token';
1515

1616
export default (type, params) => {
1717
switch (type) {
1818
case AUTH_LOGIN:
1919
const { username, password } = params;
20-
const request = new Request(`${login_uri}`, {
20+
const request = new Request(authenticationTokenUri, {
2121
method: 'POST',
2222
body: JSON.stringify({ email: username, password }),
2323
headers: new Headers({ 'Content-Type': 'application/json' }),

core/filters.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -975,7 +975,7 @@ A constant score query filter is basically a class implementing the `ApiPlatform
975975
and the `ApiPlatform\Core\Bridge\Elasticsearch\DataProvider\Filter\FilterInterface`. API Platform includes a convenient
976976
abstract class implementing this last interface and providing utility methods: `ApiPlatform\Core\Bridge\Elasticsearch\DataProvider\Filter\AbstractFilter`.
977977

978-
Suppose you want to use the [match filter](https://api-platform.com/docs/core/filters/#match-filter) on a property named `$fullName` and you want to add the [and operator](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html#query-dsl-match-query-boolean) to your query:
978+
Suppose you want to use the [match filter](#match-filter) on a property named `$fullName` and you want to add the [and operator](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html#query-dsl-match-query-boolean) to your query:
979979

980980
```php
981981
<?php

core/jwt.md

Lines changed: 110 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,142 @@
11
# JWT Authentication
22

33
> [JSON Web Token (JWT)](https://jwt.io/) is a JSON-based open standard ([RFC 7519](https://tools.ietf.org/html/rfc7519)) for creating access tokens that assert some number of claims. For example, a server could generate a token that has the claim "logged in as admin" and provide that to a client. The client could then use that token to prove that he/she is logged in as admin. The tokens are signed by the server's key, so the server is able to verify that the token is legitimate. The tokens are designed to be compact, URL-safe and usable especially in web browser single sign-on (SSO) context.
4-
> - [Wikipedia](https://en.wikipedia.org/wiki/JSON_Web_Token)
4+
>
5+
> [Wikipedia](https://en.wikipedia.org/wiki/JSON_Web_Token)
56
67
API Platform allows to easily add a JWT-based authentication to your API using [LexikJWTAuthenticationBundle](https://github.com/lexik/LexikJWTAuthenticationBundle).
7-
To install this bundle, [just follow its documentation](https://github.com/lexik/LexikJWTAuthenticationBundle/blob/master/Resources/doc/index.md).
88

99
## Installing LexikJWTAuthenticationBundle
1010

11-
`LexikJWTAuthenticationBundle` requires your application to have a properly configured user provider.
12-
You can either use the [Doctrine user provider](https://symfony.com/doc/current/security/user_provider.html#entity-user-provider) provided
13-
by Symfony (recommended), [create a custom user provider](https://symfony.com/doc/current/security/user_provider.html#creating-a-custom-user-provider)
14-
or use [API Platform's FOSUserBundle integration](fosuser-bundle.md).
11+
We begin by installing the bundle:
1512

16-
Here's a sample configuration using the data provider provided by FOSUserBundle:
13+
$ docker-compose exec php composer require jwt-auth
14+
15+
Then we need to generate the public and private keys used for signing JWT tokens. If you're using the [API Platform distribution](../distribution/index.md), you may run this from the project's root directory:
16+
17+
$ docker-compose exec php sh -c '
18+
set -e
19+
apk add openssl
20+
mkdir -p config/jwt
21+
jwt_passhrase=$(grep ''^JWT_PASSPHRASE='' .env | cut -f 2 -d ''='')
22+
echo "$jwt_passhrase" | openssl genpkey -out config/jwt/private.pem -pass stdin -aes256 -algorithm rsa -pkeyopt rsa_keygen_bits:4096
23+
echo "$jwt_passhrase" | openssl pkey -in config/jwt/private.pem -passin stdin -out config/jwt/public.pem -pubout
24+
setfacl -R -m u:www-data:rX -m u:"$(whoami)":rwX config/jwt
25+
setfacl -dR -m u:www-data:rX -m u:"$(whoami)":rwX config/jwt
26+
'
27+
28+
This takes care of using the correct passphrase to encrypt the private key, and setting the correct permissions on the
29+
keys allowing the web server to read them.
30+
31+
If you want the keys to be auto generated in `dev` environment, see an example in the [docker-entrypoint script of api-platform/demo](https://github.com/api-platform/demo/blob/master/api/docker/php/docker-entrypoint.sh).
32+
33+
The keys should not be checked in to the repository (i.e. it's in `api/.gitignore`). However, note that a JWT token could
34+
only pass signature validation against the same pair of keys it was signed with. This is especially relevant in a production
35+
environment, where you don't want to accidentally invalidate all your clients' tokens at every deployment.
36+
37+
For more information, refer to [the bundle's documentation](https://github.com/lexik/LexikJWTAuthenticationBundle/blob/master/Resources/doc/index.md)
38+
or read a [general introduction to JWT here](https://jwt.io/introduction/).
39+
40+
We're not done yet! Let's move on to configuring the Symfony SecurityBundle for JWT authentication.
41+
42+
## Configuring the Symfony SecurityBundle
43+
44+
It is necessary to configure a user provider. You can either use the [Doctrine entity user provider](https://symfony.com/doc/current/security/user_provider.html#entity-user-provider)
45+
provided by Symfony (recommended), [create a custom user provider](https://symfony.com/doc/current/security/user_provider.html#creating-a-custom-user-provider)
46+
or use [API Platform's FOSUserBundle integration](fosuser-bundle.md) (not recommended).
47+
48+
If you choose to use the Doctrine entity user provider, start by [creating your `User` class](https://symfony.com/doc/current/security.html#a-create-your-user-class).
49+
50+
Then update the security configuration:
1751

1852
```yaml
19-
# app/config/packages/security.yaml
53+
# api/config/packages/security.yaml
2054
security:
2155
encoders:
22-
FOS\UserBundle\Model\UserInterface: bcrypt
23-
24-
role_hierarchy:
25-
ROLE_READER: ROLE_USER
26-
ROLE_ADMIN: ROLE_READER
56+
App\Entity\User:
57+
algorithm: argon2i
2758

59+
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
2860
providers:
29-
fos_userbundle:
30-
id: fos_user.user_provider.username_email
61+
# used to reload user from session & other features (e.g. switch_user)
62+
app_user_provider:
63+
entity:
64+
class: App\Entity\User
65+
property: email
3166

3267
firewalls:
33-
login:
34-
pattern: ^/login
68+
dev:
69+
pattern: ^/_(profiler|wdt)
70+
security: false
71+
main:
3572
stateless: true
3673
anonymous: true
37-
provider: fos_userbundle
74+
provider: app_user_provider
3875
json_login:
39-
check_path: /login_check
76+
check_path: /authentication_token
4077
username_path: email
4178
password_path: password
4279
success_handler: lexik_jwt_authentication.handler.authentication_success
4380
failure_handler: lexik_jwt_authentication.handler.authentication_failure
44-
45-
main:
46-
pattern: ^/
47-
provider: fos_userbundle
48-
stateless: true
49-
anonymous: true
5081
guard:
5182
authenticators:
5283
- lexik_jwt_authentication.jwt_token_authenticator
84+
```
85+
86+
You must also declare the route used for `/authentication_token`:
87+
88+
```yaml
89+
# api/config/routes.yaml
90+
authentication_token:
91+
path: /authentication_token
92+
methods: ['POST']
93+
```
94+
95+
If you want to avoid loading the `User` entity from database each time a JWT token needs to be authenticated, you may consider using
96+
the [database-less user provider](https://github.com/lexik/LexikJWTAuthenticationBundle/blob/master/Resources/doc/8-jwt-user-provider.md) provided by LexikJWTAuthenticationBundle. However, it means you will have to fetch the `User` entity from the database yourself as needed (probably through the Doctrine EntityManager).
5397

98+
Refer to the section on [Security](security.md) to learn how to control access to API resources and operations. You may
99+
also want to [configure Swagger UI for JWT authentication](#documenting-the-authentication-mechanism-with-swaggeropen-api).
100+
101+
### Adding Authentication to an API Which Uses a Path Prefix
102+
103+
If your API uses a [path prefix](https://symfony.com/doc/current/routing/external_resources.html#prefixing-the-urls-of-imported-routes), the security configuration would look something like this instead:
104+
105+
```yaml
106+
# api/config/packages/security.yaml
107+
security:
108+
encoders:
109+
App\Entity\User:
110+
algorithm: argon2i
111+
112+
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
113+
providers:
114+
# used to reload user from session & other features (e.g. switch_user)
115+
app_user_provider:
116+
entity:
117+
class: App\Entity\User
118+
property: email
119+
120+
firewalls:
54121
dev:
55-
pattern: ^/(_(profiler|wdt)|css|images|js)/
122+
pattern: ^/_(profiler|wdt)
56123
security: false
57-
58-
access_control:
59-
- { path: ^/login, role: IS_AUTHENTICATED_ANONYMOUSLY }
60-
- { path: ^/books, roles: [ ROLE_READER ] }
61-
- { path: ^/, roles: [ ROLE_READER ] }
124+
api:
125+
pattern: ^/api/
126+
stateless: true
127+
anonymous: true
128+
provider: app_user_provider
129+
guard:
130+
authenticators:
131+
- lexik_jwt_authentication.jwt_token_authenticator
132+
main:
133+
anonymous: true
134+
json_login:
135+
check_path: /authentication_token
136+
username_path: email
137+
password_path: password
138+
success_handler: lexik_jwt_authentication.handler.authentication_success
139+
failure_handler: lexik_jwt_authentication.handler.authentication_failure
62140
```
63141

64142
## Documenting the Authentication Mechanism with Swagger/Open API
@@ -84,7 +162,7 @@ The "Authorize" button will automatically appear in Swagger UI.
84162
### Adding a New API Key
85163

86164
All you have to do is configure the API key in the `value` field.
87-
By default, [only the authorization header mode is enabled](https://github.com/lexik/LexikJWTAuthenticationBundle/blob/master/Resources/doc/index.md#2-use-the-token) in [LexikJWTAuthenticationBundle](https://github.com/lexik/LexikJWTAuthenticationBundle).
165+
By default, [only the authorization header mode is enabled](https://github.com/lexik/LexikJWTAuthenticationBundle/blob/master/Resources/doc/index.md#2-use-the-token) in LexikJWTAuthenticationBundle.
88166
You must set the [JWT token](https://github.com/lexik/LexikJWTAuthenticationBundle/blob/master/Resources/doc/index.md#1-obtain-the-token) as below and click on the "Authorize" button.
89167

90168
```
@@ -93,7 +171,6 @@ Bearer MY_NEW_TOKEN
93171
94172
![Screenshot of API Platform with the configuration API Key](images/JWTConfigureApiKey.png)
95173
96-
97174
## Testing with Behat
98175
99176
Let's configure Behat to automatically send an `Authorization` HTTP header containing a valid JWT token when a scenario is marked with a `@login` annotation. Edit `features/bootstrap/FeatureContext.php` and add the following methods:

core/security.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Security
22

3+
If you have yet to set up authentication for your API, refer to the section on [JWT authentication](jwt.md).
4+
35
To completely disable some operations from your application, refer to the [disabling operations](operations.md#enabling-and-disabling-operations)
46
section.
57

0 commit comments

Comments
 (0)