Expose a REST API to different kinds of users with Api-Platform [Part 2/4]

Benoit POLASZEK
2 min readJun 3, 2019

--

In the first part, we have set up a sample online shop with authenticated areas under /admin and /shop. Now we will create a REST API, both accessible within Ajax calls and from the outside.

Get started

First of all, require api-platform.

composer require api

Expose an entity

Then, create an Order entity with the help of the maker:

bin/console make:entity

Choose Yes for using it as an API Platform resource and add the following fields:

  • customer as a ManyToOne relationship to App\Entity\Customer (not nullable)
  • product as a string (not nullable)
  • fraudulent as a boolean (not nullable).

The purpose of this last field is to be accessible from Employees only (we’ll see that later).

Update your database schema:

bin/console doctrine:schema:update --force

And insert by hand 2 sample orders:

  • One for bob@customer
  • Another one alice@customer.

Open /api/orders.jsonld in your browser and you should be able to play with the endpoint, and see the 2 orders you inserted. At this time, access to the API does not require being authenticated.

Hint: if you run into a MySQL issue, it may be caused by the word “order” being a reserved keyword in MySql. To escape the table name properly, add the following annotation on top of your Order entity:

@ORM\Table("`order`")

Restrict Api access with a JSON web token

Now, require lexik/jwt-authentication-bundle:

composer require jwt-auth

And setup it as described in the docs. Don’t forget to report your passphrase accordingly in your .env file.

Is it done? Great! Since we’re using several user classes, we need to tell the api firewall which user provider to choose. What becomes interesting now is that it should be accessible for both Employees and Customers. We can then create a chained provider, and tell our firewall to use it. Your security.yaml should look like this:

# config/packages/security.yaml
security:
encoders:
App\Entity\Employee:
algorithm: plaintext
App\Entity\Customer:
algorithm: plaintext
providers:
employees:
entity:
class: App\Entity\Employee
property: email
customers:
entity:
class: App\Entity\Customer
property: email
chained:
chain:
providers: [employees, customers]

firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
api_login:
pattern: ^/api/login
stateless: true
anonymous: true
provider: chained
json_login:
check_path: /api/login_check
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
api:
pattern: ^/api
stateless: true
provider: chained
guard:
authenticators:
- lexik_jwt_authentication.jwt_token_authenticator
admin:
pattern: ^/admin
anonymous: true
provider: employees
guard:
authenticators:
- App\Security\EmployeeAuthenticator
shop:
pattern: ^/shop
anonymous: true
provider: customers
guard:
authenticators:
- App\Security\CustomerAuthenticator
main:
anonymous: true
access_control:
- { path: ^/api/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
- { path: ^/admin/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin, roles: IS_AUTHENTICATED_FULLY }
- { path: ^/shop/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/shop, roles: IS_AUTHENTICATED_FULLY }

Now, hit /api/orders.jsonld again: you should encounter a 401 response, asking you to provide a correct JWT. To get one, issue a POST request to /api/login_check with the following body:

{"username":"john@employee","password":"123456"}

The API should give you a token you can use in Authorization headers in the next requests.

You can also test this with a bob@customer’s credentials, to ensure a Customer can also have access to the Api. But, wait! Doesn’t it look like Bob has access to Alice’s orders? How can we deal with this?

Previous | Next

--

--