Silex / Symfony: SecurityServiceProvider voor REST-API

Overzicht

Sponsored by: Vacatures door Monsterboard

Jasper DS

Jasper DS

26/11/2014 15:45:35
Anchor link
Hoi,

ik bouw een REST-API op silex en wil mijn API beveiligen via de SecurityServiceProvider component van Symfony. Mijn situatie is momenteel de volgende:

Een call naar de API ziet er zo uit:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
http://domein.be/members/1?token=a08670ff00ab376dfca8a7542dcce81626b2b469


Silex koppelt een request aan deze url en stuurt de request naar de juist controller en daar wordt alles afgehandeld tot er uiteindelijk een JSON-string terug gestuurd wordt.

Nu zou ik in die controller eerst kunnen checken of de token valid is. Maar aangezien ik veel url's heb en later ook gebruik wil maken van "Roles en rights" wil ik dit automatisch via een firewall van de SecurityServiceProvider laten lopen.

Dat stukje code ziet er zo uit:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
$app
->register(new Silex\Provider\SecurityServiceProvider(), array(
        'security.firewalls' => array(
            'members' => array(
                'pattern' => '^/members',
                'http' => true,              
                'stateless' => true,
                'users' => $app->share(function () use ($app) {
                    return new Jds\Security\WebserviceUserProvider($app);
                }),
            ),
        )
    ));

?>


Wat deze code doet, is alle urls van /members opvangen dus onze call van hierboven heeft ook een match met deze firewall. Momenteel staat de "authentication" ingesteld op http maar ik wil eigenlijk mijn GET parameter "token" gebruiken. Laat ons zeggen dat dit heel erg lijkt op wat ik wil bereiken maar ik weet niet hoe ik dit in deze firewall kan implementeren.

Ik heb ook al gekeken naar een "form" oplossing i.d.p.v. de http oplossing maar dat wil zeggen dat ik een url /authentication moet maken waar ik een controller aankoppelt die de authentication voor zich neemt en ik weet niet of dat wel de bedoeling is van het firewall concept.

Hoe beveilig ik mijn API via de firewall van Symfony in Silex?

Alvast bedankt!
Gewijzigd op 26/11/2014 15:46:20 door Jasper DS
 
PHP hulp

PHP hulp

21/12/2024 16:37:34
 
Jasper DS

Jasper DS

02/12/2014 16:52:33
Anchor link
Hoi,

ik heb inmiddels de FOSUserBundle en de FOSRestBundle opgezet en werkend gekregen. Ik heb ook de api key authentication geïmplementeerd en aangepast en alles werkt al maar nog niet zoals ik het wil.

Mijn url's
- GET http://domein.be/api/v1/activities.json --> JSON collection van activities met veld1, veld2 en veld3
- GET http://domein.be/api/v1/activities/1.json --> JSON object van veld1, veld2 en veld3
- GET http://domein.be/api/v1/activities.json?username=username&password=password --> Indien user bestaat en credentials oké zijn: JSON collection van activities met veld1, veld2, veld3 EN veld4 , veld5
- GET http://domein.be/api/v1/activities/1.json?username=username&password=password --> Indien user bestaat en credentials oké zijn: JSON Object van activity met veld1, veld2, veld3 EN veld4 , veld5
- POST http://domein.be/api/v1/activities.json?username=username&password=password + JSON param body --> Indien user bestaat en credentials... --> Activity aanmaken
- ...

- Via webbrowser: http://domein.be/api/doc --> NelmioApiDocBundle --> Beveiligd via een formulier

Mijn documentatie heb ik beveiligd via de standaard formlogin van Symfony:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# app/config/security.yml
security:
    encoders:
        FOS\UserBundle\Model\UserInterface: sha512

    role_hierarchy:
        ROLE_ADMIN:       ROLE_USER
        ROLE_SUPER_ADMIN: ROLE_ADMIN

    providers:
        fos_userbundle:
            id: fos_user.user_provider.username

    firewalls:
        api_doc:
            pattern: ^/api/doc
            form_login:
                login_path: doc_login
                check_path: doc_check
                provider: fos_userbundle
                csrf_provider: form.csrf_provider
                # login success redirecting options (read further below)
                target_path_parameter: NelmioApiDocBundle
            logout:       true
            anonymous:    true

    access_control:
        - { path: ^/api/doc/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/api/doc/logout$, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/api/doc, role: ROLE_USER}

        #- { path: ^/api/v1/activities, role: IS_AUTHENTICATED_ANONYMOUSLY}

        - { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }


Mijn eigenlijke API beveilig ik via api key authentication die ik heb omgebouwd om te werken met username en password.
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
public_api:
            pattern: ^/api/v1/activities
            methods: GET
            security: false

        api:
            pattern: ^/api/v1
            stateless: true
            simple_preauth:
                authenticator: apikey_authenticator


De APIKeyAuthenticator:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
<?php

namespace Jds\UserBundle\Security;

use Symfony\Component\Security\Core\Authentication\SimplePreAuthenticatorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\HttpFoundation\Response;

class ApiKeyAuthenticator implements SimplePreAuthenticatorInterface, AuthenticationFailureHandlerInterface
{
    protected $userProvider;
    protected $encoder;

    public function __construct($userProvider, $encoder)
    {

        $this->userProvider = $userProvider;
        $this->encoder = $encoder;
    }


    public function createToken(Request $request, $providerKey)
    {


        // look for an credentials query parameter
        $credentials = array(
            'username' => $request->query->get('username'),
            'password' => $request->query->get('password')
            );


        // or if you want to use an "credentials" header, then do something like this:
        // $credentials = $request->headers->get('credentials');


        if (count(array_filter($credentials)) != 2) {
            throw new BadCredentialsException('No API key found');
        }


        return new PreAuthenticatedToken(
            'anon.',
            $credentials,
            $providerKey
        );
    }


    public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
    {

        $credentials = $token->getCredentials();

        try {
            $user = $this->userProvider->loadUserByUsername($credentials['username']);
        }

        catch(UsernameNotFoundException $e) {
            throw new AuthenticationException(
                sprintf('User "%s" does not exist.', $credentials['username'])
            );
        }


        $encoder = $this->encoder->getEncoder($user);
        $encoded_pass = $encoder->encodePassword($credentials['password'], $user->getSalt());

        if($user->getPassword() == $encoded_pass) {
            return new PreAuthenticatedToken(
                $user,
                $credentials,
                $providerKey,
                $user->getRoles()
            );
        }
else {
            throw new AuthenticationException(
                sprintf('User "%s" does not exist.', $credentials['username'])
            );
        }
    }


    public function supportsToken(TokenInterface $token, $providerKey)
    {

        return $token instanceof PreAuthenticatedToken && $token->getProviderKey() === $providerKey;
    }


    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
    {


        //return new Response("Authentication Failed.", 403);*/

        throw new AuthenticationException('Invalid username or password');

    }
}

?>


Zoals jullie bij de URL's kunnen zien moet GET api/v1/activities.json voor iedereen toegankelijk zijn zonder credentials en dat heb ik in security.yml opgelost door de public_api firewall.

Dat is nog niet de oplossing die ik wens, ik wil indien er geen credentials aanwezig zijn enkel veld1 veld2 en veld 3 terugsturen en indien er wel valid credentials zijn ook nog veld4 en veld5 meesturen maar dat is momenteel op geen enkele manier mogelijk.

Volgens mij loopt het mis bij de onfail functie. Daar maak ik een exception aan wat perfect werk voor alle url's die wel sowieso een authentication nodig hebben. Dit omzeilen door een firewall voor get activities aan te maken werkt maar dan is er totaal geen authentication meer mogelijk omdat de credentials niet meer gelezen worden door de ApiKeyAuthenticator.
Gewijzigd op 02/12/2014 17:10:07 door Jasper DS
 
- Ariën  -
Beheerder

- Ariën -

13/03/2019 09:18:17
Anchor link
Gesloten na spam-kick die inmiddels verwijderd is.
 
 

Dit topic is gesloten.



Overzicht

 
 

Om de gebruiksvriendelijkheid van onze website en diensten te optimaliseren maken wij gebruik van cookies. Deze cookies gebruiken wij voor functionaliteiten, analytische gegevens en marketing doeleinden. U vindt meer informatie in onze privacy statement.