Autorizácia
Každý endpoint má objekt autorizácie, tretí argument addApi(). Implementuje ApiAuthorizationInterface s dvoma metódami: authorized(): bool a getErrorMessage(): ?string.
Keď authorized() vráti false, error handler odpovie 401 s {"status":"error","message":"..."} a hláškou z getErrorMessage(). Výnimka vyhodená z authorized() vráti 500 s textom výnimky.
Vstavané triedy
| Trieda | Token číta z | Konštruktor |
|---|---|---|
NoAuthorization | nič, prejde vždy | žiadny |
BearerTokenAuthorization | Authorization: Bearer <token> | (TokenRepositoryInterface, IpDetectorInterface) |
HeaderApiKeyAuthentication | hlavičky, napríklad X-API-Key | (string $headerName, TokenRepositoryInterface, IpDetectorInterface) |
QueryApiKeyAuthentication | query parametra, napríklad ?api_key= | (string $queryParamName, TokenRepositoryInterface, IpDetectorInterface) |
CookieApiKeyAuthentication | cookie | (string $cookieName, TokenRepositoryInterface, IpDetectorInterface) |
BasicAuthentication | mena a hesla v URL požiadavky | (array $users, IRequest $httpRequest) |
Tokenové triedy zdieľajú TokenAuthorization: token musí byť platný v repozitári a potom IP klienta musí prejsť obmedzením IP tokenu.
Tokeny a obmedzenie IP
TokenRepositoryInterface má validToken(string $token): bool a ipRestrictions(string $token): ?string. StaticTokenRepository berie pole token => obmedzenie:
application:
mapping:
Api: Tomaj\NetteApi\Presenters\*Presenter
parameters:
# token => allowed IPs: '*' for any, one IP, a list, or an IPv4 CIDR range
apiTokens:
secret-token: '*'
office-token: '10.0.0.0/8'
apiUsers:
admin: change-me
services:
router: App\RouterFactory::create
apiPresenter:
factory: Tomaj\NetteApi\Presenters\ApiPresenter
- Tomaj\NetteApi\Output\Configurator\EnvConfigurator
- Tomaj\NetteApi\Error\DefaultErrorHandler
# the only IpDetectorInterface service, so it is autowired everywhere
- App\RemoteAddrIpDetector
tokens: Tomaj\NetteApi\Misc\StaticTokenRepository(%apiTokens%)
bearer: Tomaj\NetteApi\Authorization\BearerTokenAuthorization(@tokens)
apiKey: Tomaj\NetteApi\Authorization\HeaderApiKeyAuthentication('X-API-Key', @tokens)
basic: App\BasicCredentialsAuthorization(%apiUsers%)
rateLimit:
factory: App\FileRateLimit(directory: %tempDir%)
# otherwise Nette DI passes it as the 4th argument of every addApi() call below
autowired: false
- App\Api\WhoAmIHandler
apiDecider:
factory: Tomaj\NetteApi\ApiDecider
setup:
# Authorization: Bearer secret-token
- addApi(Tomaj\NetteApi\EndpointIdentifier('GET', 1, 'bearer'), App\Api\WhoAmIHandler, @bearer)
# X-API-Key: secret-token
- addApi(Tomaj\NetteApi\EndpointIdentifier('GET', 1, 'api-key'), App\Api\WhoAmIHandler, @apiKey)
# curl -u admin:change-me
- addApi(Tomaj\NetteApi\EndpointIdentifier('GET', 1, 'basic'), App\Api\WhoAmIHandler, @basic)
# no authorization, 3 requests per minute per IP
- addApi(Tomaj\NetteApi\EndpointIdentifier('GET', 1, 'limited'), App\Api\WhoAmIHandler, Tomaj\NetteApi\Authorization\NoAuthorization(), @rateLimit)| Obmedzenie | Znamená |
|---|---|
* alebo prázdny reťazec | ľubovoľná IP |
1.2.3.4 | presne táto IP |
1.2.3.4, 5.6.7.8 | ktorákoľvek z uvedených, oddelené čiarkami, medzerami alebo novými riadkami |
10.0.0.0/8 | IPv4 rozsah |
null | nikdy, Invalid IP |
V reálnej aplikácii implementujte TokenRepositoryInterface nad vašou databázou.
Známe obmedzenia v 3.x
- Detekciu IP sa dá podvrhnúť. Vstavaný
IpDetectoruprednostňuje hlavičkyClient-IPaX-Forwarded-For, ktoré môže poslať ktorýkoľvek klient. Používajte ho len za proxy, ktorá ich prepisuje, alebo vlastný detektor akoRemoteAddrIpDetectorz príkladu autorizácie. - Počíta sa len prvý rozsah. V zozname kontrola skončí pri prvej položke s
/, takže10.0.0.0/8, 192.168.0.0/16sa s druhým rozsahom nikdy nezhoduje. Dajte rozsah na koniec, alebo jeden rozsah na token. - Číselné tokeny sa nikdy nezhodujú v
StaticTokenRepository: PHP zmení kľúč ako'12345'na celé číslo, ktoré sa s reťazcovým tokenom nikdy nerovná. Používajte kľúče s písmenom. - BasicAuthentication číta meno z URL požiadavky, ktorú
nette/http3.2 a novší nechá prázdnu, takže tam nikdy neprejde. Príklad obsahujeBasicCredentialsAuthorization, ktorý číta hlavičkuAuthorization.
Od verzie 3.6.0 porovnávajú BasicAuthentication a StaticTokenRepository heslá a tokeny cez hash_equals(), v konštantnom čase, takže čas odpovede neprezradí, aká časť tokenu bola správna. Ktoré hodnoty sa zhodujú, je rovnaké ako predtým. Vo vlastnom repozitári tokenov alebo autorizácii porovnávajte tajomstvá tiež cez hash_equals().
Vlastná autorizácia
<?php
declare(strict_types=1);
namespace App\Api;
use Nette\Security\User;
use Tomaj\NetteApi\Authorization\ApiAuthorizationInterface;
final class AdminOnlyAuthorization implements ApiAuthorizationInterface
{
public function __construct(private User $user) {}
public function authorized(): bool
{
return $this->user->isInRole('admin');
}
public function getErrorMessage(): ?string
{
return 'Admins only';
}
}Časté otázky
Ako vyzerá telo odpovede 401?
{"status":"error","message":"Token doesn't exists or isn't active"} alebo iná hláška z getErrorMessage(). Zmeniť ju môžete vo vlastnom error handleri.
Môže jeden endpoint prijať bearer token aj API kľúč?
Napíšte triedu, ktorá skúsi oboje: zavolá authorized() na každej a zapamätá si, ktorú hlášku vrátiť. Príklad autorizácie ukazuje každú metódu na vlastnom endpointe.
Pošle konzola môj token?
Áno, webová konzola má pole pre bearer token, API kľúč v hlavičke, query aj cookie a meno s heslom pre Basic.
Ako povedať OpenAPI, že endpoint potrebuje token?
OpenApiHandler prečíta triedu autorizácie a pridá zodpovedajúcu bezpečnostnú schému. Pozrite si OpenAPI.
Kde obmedzím, ako často môže token volať API?
To je obmedzenie požiadaviek, nastavuje sa pre každý endpoint vedľa autorizácie.