L o g o w a n i e u ż y t k o w n i k ó w w R E S T A P I n a
m i k r o s e r w i s a c h
G R U PA Q P O N Y B L I X
P O N A D 3 0 0 0 0 0 0 U Ż Y T K O W N I K Ó W M I E S I Ę C Z N I E
J W T
J S O N W e b T o k e n
J s o n W e b T o k e n
JSON Web Token (JWT) is an open standard (RFC 7519) that
defines a compact and self-contained way for securely transmitting
information between parties as a JSON object.
J s o n W e b T o k e n
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3O
DkwIiwibmFtZSI6IkdyemVnb3J6IiwicGhwZXJzIjoyMDE5LCJpYXQiO
jE1MTYyMzkwMjJ9.fDuS4X19nQ9B3c9QWVsbYJqI4ckBXetARlxZp
RXHOOw
J s o n W e b T o k e n
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3O
DkwIiwibmFtZSI6IkdyemVnb3J6IiwicGhwZXJzIjoyMDE5LCJpYXQiO
jE1MTYyMzkwMjJ9.fDuS4X19nQ9B3c9QWVsbYJqI4ckBXetARlxZp
RXHOOw
J s o n W e b T o k e n
J s o n W e b T o k e n
eyJhbGciOiJIUzI1NiIsInR5cCI6
IkpXVCJ9
J s o n W e b T o k e n
eyJhbGciOiJIUzI1NiIsInR5cCI6
IkpXVCJ9
{
"alg": "HS256",
"typ": "JWT"
}
J s o n W e b T o k e n
eyJhbGciOiJIUzI1NiIsInR5cCI6
IkpXVCJ9
eyJzdWIiOiIxMjM0NTY3ODkwI
iwibmFtZSI6IkdyemVnb3J6Iiwi
cGhwZXJzIjoyMDE5LCJpYXQi
OjE1MTYyMzkwMjJ9
{
"alg": "HS256",
"typ": "JWT"
}
J s o n W e b T o k e n
eyJhbGciOiJIUzI1NiIsInR5cCI6
IkpXVCJ9
eyJzdWIiOiIxMjM0NTY3ODkwI
iwibmFtZSI6IkdyemVnb3J6Iiwi
cGhwZXJzIjoyMDE5LCJpYXQi
OjE1MTYyMzkwMjJ9
{
"alg": "HS256",
"typ": "JWT"
}
{
"sub": "1234567890",
"name": "Grzegorz",
"phpers": 2019,
"iat": 1516239022
}
J s o n W e b T o k e n
eyJhbGciOiJIUzI1NiIsInR5cCI6
IkpXVCJ9
eyJzdWIiOiIxMjM0NTY3ODkwI
iwibmFtZSI6IkdyemVnb3J6Iiwi
cGhwZXJzIjoyMDE5LCJpYXQi
OjE1MTYyMzkwMjJ9
fDuS4X19nQ9B3c9QWVsbYJq
I4ckBXetARlxZpRXHOOw
{
"alg": "HS256",
"typ": "JWT"
}
{
"sub": "1234567890",
"name": "Grzegorz",
"phpers": 2019,
"iat": 1516239022
}
J s o n W e b T o k e n
eyJhbGciOiJIUzI1NiIsInR5cCI6
IkpXVCJ9
eyJzdWIiOiIxMjM0NTY3ODkwI
iwibmFtZSI6IkdyemVnb3J6Iiwi
cGhwZXJzIjoyMDE5LCJpYXQi
OjE1MTYyMzkwMjJ9
fDuS4X19nQ9B3c9QWVsbYJq
I4ckBXetARlxZpRXHOOw
{
"alg": "HS256",
"typ": "JWT"
}
{
"sub": "1234567890",
"name": "Grzegorz",
"phpers": 2019,
"iat": 1516239022
}
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
PHPers2019
)
H E A D E R
T y p e A l g o r i t h m
HS256
HS384
HS512
RS256
RS384
RS512
ES256
ES384
ES512
PS256
PS384
PS512
J W T
PAY L O A D
R e g i s t e r e d C l a i m N a m e s
PAY L O A D
Issuer (iss)
Subject (sub)
Audience (aud)
Expiration Time (exp)
Not Before (nbf)
Issued At (iat)
JWT ID (jtd)
R e g i s t e r e d C l a i m N a m e s
PAY L O A D
P u b l i c C l a i m N a m e sP r i v a t e C l a i m N a m e s
S I G N AT U R E
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
S I G N AT U R E
RSASHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
-----BEGIN PUBLIC KEY-----
...
,
-----BEGIN RSA PRIVATE KEY-----
...
)
A U T H O R I Z AT I O N
Authorization: Bearer eyJhbGci...lxZpRXHOOw
O A u t h
O p e n A u t h o r i z a t i o n
OAuth (Open Authorization) is an open standard for token-based
authentication and authorization on the Internet.
R E M E B E R
A u t h o r i z a t i o nA u t h e n t i c a t i o n
R E M E B E R
A u t h o r i z a t i o nA u t h e n t i c a t i o n
W h o a r e y o u ?
R E M E B E R
A u t h o r i z a t i o nA u t h e n t i c a t i o n
A r e y o u a l l o w e d t o d o t h a t ?W h o a r e y o u ?
Resource Server
Client App Resource Server
Get user’s data
Client App Resource Server
Get user’s data
Return user’s data
Client App Resource Server
Get user’s data
Return user’s data
Client App Resource Server
Get user’s data
Return user’s data
Client App Resource Server
Client App Resource Server
Client App Resource Server
Access Token
Client App Resource Server
Access Token
Get user’s data
Client App Resource Server
Access Token
Access Token
Get user’s data
Client App Resource Server
Access Token
Access Token
Get user’s data
Access Token
Client App Resource Server
Access Token
Access Token
Get user’s data
Access Token
Verify
Return user’s data
Client App Resource Server
Access Token
Access Token
Get user’s data
Access Token
Verify
Return user’s data
Client App Resource Server
Access Token
Access Token
Get user’s data
Access Token
Verify
Client App Authorization Server
Client App Authorization Server
Access Token
G
enerate
Issue Access Token
Client App Authorization Server
Access Token
G
enerate
Issue Access Token
Client App Authorization Server
Access Token Access Token
G
enerate
Client App
Resource Server
Authorization Server
Client App
Resource Server
Authorization Server
Access Token
G
enerate
Client App
Resource Server
Issue Access Token
Authorization Server
Access Token
G
enerate
Client App
Resource Server
Access Token
Issue Access Token
Authorization Server
Access Token
G
enerate
Client App
Resource Server
Access Token
Get user’s data
Issue Access Token
Authorization Server
Access Token
G
enerate
Client App
Resource Server
Access Token
Access Token
Get user’s data
Issue Access Token
Authorization Server
Access Token
G
enerate
Client App
Resource Server
Access Token
Access Token
Get user’s data Access Token
Issue Access Token
Authorization Server
Access Token
G
enerate
Client App
Resource Server
Access Token
Access Token
Get user’s data Access Token
Verify
Issue Access Token
Authorization Server
Access Token
G
enerate
Return user’s data
Client App
Resource Server
Access Token
Access Token
Get user’s data Access Token
Verify
Issue Access Token
Authorization Server
Access Token
G
enerate
Return user’s data
Client App
Resource Server
Access Token
Access Token
Get user’s data Access Token
Verify
Issue Access Token
Authorization Server
Access Token
G
enerate
S S O
S i n g l e S i g n - O n
G o o g l e S i g n I n F l o w
G o o g l e S i g n I n F l o w
1. Mobile: Użytkownik klika przycisk Zaloguj z Google
G o o g l e S i g n I n F l o w
1. Mobile: Użytkownik klika przycisk Zaloguj z Google
2. Mobile: Otwieramy formularz logowania Google z parametrem requestIdToken(serverClientId) żeby
dostać ID Token
G o o g l e S i g n I n F l o w
1. Mobile: Użytkownik klika przycisk Zaloguj z Google
2. Mobile: Otwieramy formularz logowania Google z parametrem requestIdToken(serverClientId) żeby
dostać ID Token
3. Mobile: User poprawnie loguje się profilem Google
G o o g l e S i g n I n F l o w
1. Mobile: Użytkownik klika przycisk Zaloguj z Google
2. Mobile: Otwieramy formularz logowania Google z parametrem requestIdToken(serverClientId) żeby
dostać ID Token
3. Mobile: User poprawnie loguje się profilem Google
4. Mobile: W odpowiedzi na poprawne zalogowanie otrzymujemy ID Token, który wysyłamy do API
G o o g l e S i g n I n F l o w
1. Mobile: Użytkownik klika przycisk Zaloguj z Google
2. Mobile: Otwieramy formularz logowania Google z parametrem requestIdToken(serverClientId) żeby
dostać ID Token
3. Mobile: User poprawnie loguje się profilem Google
4. Mobile: W odpowiedzi na poprawne zalogowanie otrzymujemy ID Token, który wysyłamy do API
5. Backend: Odbiera ID Token i sprawdza jego poprawność
G o o g l e S i g n I n F l o w
1. Mobile: Użytkownik klika przycisk Zaloguj z Google
2. Mobile: Otwieramy formularz logowania Google z parametrem requestIdToken(serverClientId) żeby
dostać ID Token
3. Mobile: User poprawnie loguje się profilem Google
4. Mobile: W odpowiedzi na poprawne zalogowanie otrzymujemy ID Token, który wysyłamy do API
5. Backend: Odbiera ID Token i sprawdza jego poprawność
6. Backend: Jeśli token jest poprawny to można z niego wyciągnąć dane o użytkowniku i na tej podstawie
stworzyć konto/albo nową sesję
G o o g l e S i g n I n F l o w
1. Mobile: Użytkownik klika przycisk Zaloguj z Google
2. Mobile: Otwieramy formularz logowania Google z parametrem requestIdToken(serverClientId) żeby
dostać ID Token
3. Mobile: User poprawnie loguje się profilem Google
4. Mobile: W odpowiedzi na poprawne zalogowanie otrzymujemy ID Token, który wysyłamy do API
5. Backend: Odbiera ID Token i sprawdza jego poprawność
6. Backend: Jeśli token jest poprawny to można z niego wyciągnąć dane o użytkowniku i na tej podstawie
stworzyć konto/albo nową sesję
7. Mobile: API zwraca odpowiedź, że logowanie jest poprawne lub niepoprawne (+ tokeny refresh/access)
F a c e b o o k S i g n I n F l o w
F a c e b o o k S i g n I n F l o w
1. Mobile: Użytkownik klika przycisk Zaloguj z Facebook
F a c e b o o k S i g n I n F l o w
1. Mobile: Użytkownik klika przycisk Zaloguj z Facebook
2. Mobile: Otwieramy formularz logowania Facebook z Facebook SDK (w zależności od tego czy jest
zainstalowana aplikacja FB to będzie formularz natywny, jeśli nie jest zainstalowana to PopUp z WebView)
F a c e b o o k S i g n I n F l o w
1. Mobile: Użytkownik klika przycisk Zaloguj z Facebook
2. Mobile: Otwieramy formularz logowania Facebook z Facebook SDK (w zależności od tego czy jest
zainstalowana aplikacja FB to będzie formularz natywny, jeśli nie jest zainstalowana to PopUp z WebView)
3. Mobile: User poprawnie loguje się profilem Facebook
F a c e b o o k S i g n I n F l o w
1. Mobile: Użytkownik klika przycisk Zaloguj z Facebook
2. Mobile: Otwieramy formularz logowania Facebook z Facebook SDK (w zależności od tego czy jest
zainstalowana aplikacja FB to będzie formularz natywny, jeśli nie jest zainstalowana to PopUp z WebView)
3. Mobile: User poprawnie loguje się profilem Facebook
4. Mobile: W odpowiedzi na poprawne zalogowanie otrzymujemy AccessToken, który wysyłamy do API
F a c e b o o k S i g n I n F l o w
1. Mobile: Użytkownik klika przycisk Zaloguj z Facebook
2. Mobile: Otwieramy formularz logowania Facebook z Facebook SDK (w zależności od tego czy jest
zainstalowana aplikacja FB to będzie formularz natywny, jeśli nie jest zainstalowana to PopUp z WebView)
3. Mobile: User poprawnie loguje się profilem Facebook
4. Mobile: W odpowiedzi na poprawne zalogowanie otrzymujemy AccessToken, który wysyłamy do API
5. Backend: Odbiera AccessToken, sprawdza jego poprawność z wykorzystaniem endpointu /user z
parametrem me jako user-id (czyli /v3.2/me) oraz dla bezpieczeństwa parametrem appsecret_proof
F a c e b o o k S i g n I n F l o w
1. Mobile: Użytkownik klika przycisk Zaloguj z Facebook
2. Mobile: Otwieramy formularz logowania Facebook z Facebook SDK (w zależności od tego czy jest
zainstalowana aplikacja FB to będzie formularz natywny, jeśli nie jest zainstalowana to PopUp z WebView)
3. Mobile: User poprawnie loguje się profilem Facebook
4. Mobile: W odpowiedzi na poprawne zalogowanie otrzymujemy AccessToken, który wysyłamy do API
5. Backend: Odbiera AccessToken, sprawdza jego poprawność z wykorzystaniem endpointu /user z
parametrem me jako user-id (czyli /v3.2/me) oraz dla bezpieczeństwa parametrem appsecret_proof
6. Backend: Jeśli token jest poprawny to można z niego wyciągnąć dane o użytkowniku i na tej podstawie
stworzyć konto/albo nową sesję.
F a c e b o o k S i g n I n F l o w
1. Mobile: Użytkownik klika przycisk Zaloguj z Facebook
2. Mobile: Otwieramy formularz logowania Facebook z Facebook SDK (w zależności od tego czy jest
zainstalowana aplikacja FB to będzie formularz natywny, jeśli nie jest zainstalowana to PopUp z WebView)
3. Mobile: User poprawnie loguje się profilem Facebook
4. Mobile: W odpowiedzi na poprawne zalogowanie otrzymujemy AccessToken, który wysyłamy do API
5. Backend: Odbiera AccessToken, sprawdza jego poprawność z wykorzystaniem endpointu /user z
parametrem me jako user-id (czyli /v3.2/me) oraz dla bezpieczeństwa parametrem appsecret_proof
6. Backend: Jeśli token jest poprawny to można z niego wyciągnąć dane o użytkowniku i na tej podstawie
stworzyć konto/albo nową sesję.
7. Mobile: Otrzymujemy odpowiedź, że logowanie jest poprawne lub niepoprawne (+ tokeny refresh/access)
U s e r S e r v i c e
$ composer require lexik/jwt-authentication-bundle
lexik_jwt_authentication:
secret_key: 'secret'
token_ttl: 3600
encoder:
signature_algorithm: 'HS512'
api_login_check:
path: /api/login_check
security:
providers:
in_memory:
memory:
users:
admin: # Test dummy user
password: password
roles: 'ROLE_ADMIN'
encoders:
SymfonyComponentSecurityCoreUserUser: plaintext
firewalls:
login:
pattern: ^/api/login
stateless: true
anonymous: true
json_login:
check_path: /api/login_check
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
provider: in_memory
api:
pattern: ^/api
stateless: true
guard:
authenticators: [lexik_jwt_authentication.jwt_token_authenticator]
access_control:
- { path: ^/api/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
curl -X POST 
https://example.com/api/login_check 
-H 'Content-Type: application/json' 
-d '{"username":"admin","password":"password"}'
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE1NjY0NjgzODQsImV4cCI6MTU2N
jQ3MTk4NCwicm9sZXMiOlsiUk9MRV9BRE1JTiJdLCJ1c2VybmFtZSI6ImFkbWluIn0.TJST6ZifM4jIZiuejvMkd0PZ
vwZWlgS6MKsloNBEUHmYTTGyZlcsJx-ol04U-cn2uAzisfv-oHLw7I7k1sqVY5V28RjUg-OBN-CtEzEAh_OL-a-
kweuO0q4KCsv5Pm1R77rahfZ2rZ1Z0j3A6SmOTqEjpVBIOBHO76Gy6hGY7uHiyVgi4hfxhBUV-
Pd1zBxule77upXX9AgWwFnvltbSD3GioUTIlu8k5kizX7_IteZAhXRs7-o0jE3oMPQi0TCQbp7wf_-bSfGmtV-
XoPnxT-
jmvWPGGjAI50_BEgwif8vwXPEn7gHN4nzwrOJd9YjVfn_9l8bsmx_ruqXX0C_FMUlDkcABDsApNJzo3dpeAnEdMYPKo
_jnVMfaV0QcBPrcfBTvldCDW67WaMDC7T9kNUAj4weB48rnjDzveyg6ymzoLanMTMq567pFBWfiVRrQluQ0QKnTtXQD
-qbg4k8wuTlNxZvd1zJ2HPa7U3gH9ZVrwK27P3_7hZzZghPFlhNh-
J_vm7nVd0H4EVIlFj6mtPjnCiXyRugM4g8HYEc_Kx_UnbL4EBkWPpzrT2X9tYQvXnWtyxeWNubHGoizPlxYOAoIZeGT
Lp0HKKSecvvrZLNjea_GP_Fo6KOCC6N4o4cy2FsFdM06Zk6pC_0R-xzOUtqc3-dBkdBrhTyVFc3gbgU"
}
{
"iat":1566468384,
"exp":1566471984,
"roles":["ROLE_ADMIN"],
"username":"admin"
}
S y m f o n y G u a r d
$ composer require symfony/security-guard
interface AuthenticatorInterface
{
public function supports(Request $request): bool;
public function getCredentials(Request $request);
public function getUser($credentials, UserProviderInterface $userProvider): ?UserInterface;
public function checkCredentials($credentials, UserInterface $user): bool;
public function createAuthenticatedToken(UserInterface $user, $providerKey): GuardTokenInterface;
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response;
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey): ?Response;
public function supportsRememberMe(): bool;
}
$ composer require league/oauth2-google
class GoogleAuthenticator implements AuthenticatorInterface
{
…
}
public function supportsRememberMe(): bool
{
return true;
}
public function checkCredentials($credentials, UserInterface $user): bool
{
return true;
}
public function supports(Request $request): bool
{
return $request->attributes->get('_route') === 'api_login_check';
}
public function getUser($credentials, UserProviderInterface $userProvider): UserInterface
{
$googleUser = $this->getClient()->fetchUserFromCredentials($credentials);
$existingUser = $this->repository->findOneBy(['googleId' => $googleUser->getId()]);
if ($existingUser) {
return $existingUser;
}
$user = $this->createUser($googleUser);
$this->em->persist($user);
$this->em->flush();
return $user;
}
protected function createUser(GoogleUser $googleUser): User
{
$user = new User();
$user->setEmail($googleUser->getEmail());
$user->setGoogleEmail($googleUser->getEmail());
$user->setGoogleId($googleUser->getId());
$user->setFirstName($googleUser->getFirstName());
$user->setLastName($googleUser->getLastName());
return $user;
}
protected function getClient(): GoogleClient
{
return $this->clients['google'];
}
public function getCredentials(Request $request): array
{
return $this->getClient()->getCredentialsFromIdToken($request->query->get('id_token', ''));
}
protected function fetchUserFromCredentials(array $credentials): GoogleUser
{
return $this->getClient()->fetchUserFromCredentials($credentials);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey): ?Response
{
// LexikBundleJWTAuthenticationBundleSecurityHttpAuthenticationAuthenticationSuccessHandler
return $this->authenticationSuccessHandler->onAuthenticationSuccess($request, $token);
}
public function onAuthenticationFailure(Request $request, AuthenticationException $authException): ?Response
{
// LexikBundleJWTAuthenticationBundleSecurityHttpAuthenticationAuthenticationFailureHandler
return $this->authenticationFailureHandler->onAuthenticationFailure($request, $authException);
}
public function start(Request $request, AuthenticationException $authException = null): Response
{
$ex = $authException ?? new AuthenticationException('Unknown error');
return $this->authenticationFailureHandler->onAuthenticationFailure($request, $ex);
}
use LeagueOAuth2ClientProviderExceptionIdentityProviderException;
use LeagueOAuth2ClientProviderGoogle;
use LeagueOAuth2ClientProviderGoogleUser;
class GoogleClient
{
public function __construct(string $clientId, string $clientSecret)
{
$this->provider = new Google([
'clientId' => $clientId,
'clientSecret' => $clientSecret,
]);
$this->clientId = $clientId;
}
public function getCredentialsFromIdToken(string $idToken): ?array
{
$request = $this->provider->getRequest('GET', 'https://oauth2.googleapis.com/tokeninfo?id_token=' . $idToken);
$response = $this->provider->getParsedResponse($request);
return $this->validateIdTokenResponse($response) ? $response : null;
}
private function validateIdTokenResponse(array $response): bool
{
return
preg_match('/^(?:https://)?accounts.google.com$/', $response['iss'] ?? '') === 1
&& ($response['aud'] ?? '') === $this->clientId
&& ($response['exp'] ?? 0) > time();
}
public function fetchUserFromCredentials($credentials): GoogleUser
{
return new GoogleUser($credentials);
}
}
AppSecurityAuthenticatorGoogleAuthenticator:
arguments:
- { google: '@AppClientGoogleClient' }
AppClientGoogleClient:
arguments:
- '%env(OAUTH_GOOGLE_ID)%'
- '%env(OAUTH_GOOGLE_SECRET)%'
$ composer require league/oauth2-facebook
class FacebookAuthenticator extends AbstractSocialAuthenticator
{
public function getCredentials(Request $request): FacebookUser {…}
public function getUser($credentials, UserProviderInterface $userProvider): UserInterface {…}
protected function getClient(): FacebookClient {…}
protected function createUser(FacebookUser $facebookUser): User {…}
protected function getService(): string {…}
}
curl -X POST 
https://example.com/api/login_check?service=google&id_token=token
curl -X POST 
https://example.com/api/login_check?service=facebook&id_token=token
E m a i l / P a s s w o r d L o g I n
security:
firewalls:
base_login:
pattern: ^/api/base_login
stateless: true
anonymous: true
provider: in_memory
form_login:
check_path: /api/base_login/login_check
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
require_previous_session: false
access_control:
- { path: ^/api/base_login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
api_base_login_check:
path: /api/base_login/login_check
curl -X POST 
https://example.com/api/base_login/login_check 
-H 'Content-Type: application/x-www-form-urlencoded' 
-d '_username=admin&_password=password'
R e f r e s h T o k e n s
$ composer require gesdinet/jwt-refresh-token-bundle
gesdinet_jwt_refresh_token:
ttl: 2592000 #30 days
ttl_update: true
user_identity_field: username
gesdinet_jwt_refresh_token:
path: /api/token/refresh
defaults: { _controller: gesdinet.jwtrefreshtoken:refresh }
security:
firewalls:
refresh:
pattern: ^/api/token/refresh
stateless: true
anonymous: true
access_control:
- { path: ^/api/token/refresh, roles: IS_AUTHENTICATED_ANONYMOUSLY }
G d z i e t e m i k r o s e r w i s y ?
P Y TA N I A ?
GRZEGORZ STAWARCZYK
grzegorz.stawarczyk@qpony.pl
@gstawarczyk
D Z I Ę K U J Ę !

Logowanie użytkowników w REST API na mikroserwisach PHPers Summit 2019

  • 1.
    L o go w a n i e u ż y t k o w n i k ó w w R E S T A P I n a m i k r o s e r w i s a c h
  • 2.
    G R UPA Q P O N Y B L I X P O N A D 3 0 0 0 0 0 0 U Ż Y T K O W N I K Ó W M I E S I Ę C Z N I E
  • 3.
  • 4.
    J S ON W e b T o k e n
  • 5.
    J s on W e b T o k e n JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object.
  • 6.
    J s on W e b T o k e n eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3O DkwIiwibmFtZSI6IkdyemVnb3J6IiwicGhwZXJzIjoyMDE5LCJpYXQiO jE1MTYyMzkwMjJ9.fDuS4X19nQ9B3c9QWVsbYJqI4ckBXetARlxZp RXHOOw
  • 7.
    J s on W e b T o k e n eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3O DkwIiwibmFtZSI6IkdyemVnb3J6IiwicGhwZXJzIjoyMDE5LCJpYXQiO jE1MTYyMzkwMjJ9.fDuS4X19nQ9B3c9QWVsbYJqI4ckBXetARlxZp RXHOOw
  • 8.
    J s on W e b T o k e n
  • 9.
    J s on W e b T o k e n eyJhbGciOiJIUzI1NiIsInR5cCI6 IkpXVCJ9
  • 10.
    J s on W e b T o k e n eyJhbGciOiJIUzI1NiIsInR5cCI6 IkpXVCJ9 { "alg": "HS256", "typ": "JWT" }
  • 11.
    J s on W e b T o k e n eyJhbGciOiJIUzI1NiIsInR5cCI6 IkpXVCJ9 eyJzdWIiOiIxMjM0NTY3ODkwI iwibmFtZSI6IkdyemVnb3J6Iiwi cGhwZXJzIjoyMDE5LCJpYXQi OjE1MTYyMzkwMjJ9 { "alg": "HS256", "typ": "JWT" }
  • 12.
    J s on W e b T o k e n eyJhbGciOiJIUzI1NiIsInR5cCI6 IkpXVCJ9 eyJzdWIiOiIxMjM0NTY3ODkwI iwibmFtZSI6IkdyemVnb3J6Iiwi cGhwZXJzIjoyMDE5LCJpYXQi OjE1MTYyMzkwMjJ9 { "alg": "HS256", "typ": "JWT" } { "sub": "1234567890", "name": "Grzegorz", "phpers": 2019, "iat": 1516239022 }
  • 13.
    J s on W e b T o k e n eyJhbGciOiJIUzI1NiIsInR5cCI6 IkpXVCJ9 eyJzdWIiOiIxMjM0NTY3ODkwI iwibmFtZSI6IkdyemVnb3J6Iiwi cGhwZXJzIjoyMDE5LCJpYXQi OjE1MTYyMzkwMjJ9 fDuS4X19nQ9B3c9QWVsbYJq I4ckBXetARlxZpRXHOOw { "alg": "HS256", "typ": "JWT" } { "sub": "1234567890", "name": "Grzegorz", "phpers": 2019, "iat": 1516239022 }
  • 14.
    J s on W e b T o k e n eyJhbGciOiJIUzI1NiIsInR5cCI6 IkpXVCJ9 eyJzdWIiOiIxMjM0NTY3ODkwI iwibmFtZSI6IkdyemVnb3J6Iiwi cGhwZXJzIjoyMDE5LCJpYXQi OjE1MTYyMzkwMjJ9 fDuS4X19nQ9B3c9QWVsbYJq I4ckBXetARlxZpRXHOOw { "alg": "HS256", "typ": "JWT" } { "sub": "1234567890", "name": "Grzegorz", "phpers": 2019, "iat": 1516239022 } HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), PHPers2019 )
  • 15.
    H E AD E R T y p e A l g o r i t h m HS256 HS384 HS512 RS256 RS384 RS512 ES256 ES384 ES512 PS256 PS384 PS512 J W T
  • 16.
    PAY L OA D R e g i s t e r e d C l a i m N a m e s
  • 17.
    PAY L OA D Issuer (iss) Subject (sub) Audience (aud) Expiration Time (exp) Not Before (nbf) Issued At (iat) JWT ID (jtd) R e g i s t e r e d C l a i m N a m e s
  • 18.
    PAY L OA D P u b l i c C l a i m N a m e sP r i v a t e C l a i m N a m e s
  • 19.
    S I GN AT U R E HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
  • 20.
    S I GN AT U R E RSASHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), -----BEGIN PUBLIC KEY----- ... , -----BEGIN RSA PRIVATE KEY----- ... )
  • 21.
    A U TH O R I Z AT I O N Authorization: Bearer eyJhbGci...lxZpRXHOOw
  • 22.
    O A ut h
  • 23.
    O p en A u t h o r i z a t i o n
  • 24.
    OAuth (Open Authorization)is an open standard for token-based authentication and authorization on the Internet.
  • 25.
    R E ME B E R A u t h o r i z a t i o nA u t h e n t i c a t i o n
  • 26.
    R E ME B E R A u t h o r i z a t i o nA u t h e n t i c a t i o n W h o a r e y o u ?
  • 27.
    R E ME B E R A u t h o r i z a t i o nA u t h e n t i c a t i o n A r e y o u a l l o w e d t o d o t h a t ?W h o a r e y o u ?
  • 30.
  • 31.
  • 32.
    Get user’s data ClientApp Resource Server
  • 33.
    Get user’s data Returnuser’s data Client App Resource Server
  • 34.
    Get user’s data Returnuser’s data Client App Resource Server
  • 35.
    Get user’s data Returnuser’s data Client App Resource Server
  • 37.
  • 38.
    Client App ResourceServer Access Token
  • 39.
    Client App ResourceServer Access Token Get user’s data
  • 40.
    Client App ResourceServer Access Token Access Token Get user’s data
  • 41.
    Client App ResourceServer Access Token Access Token Get user’s data Access Token
  • 42.
    Client App ResourceServer Access Token Access Token Get user’s data Access Token Verify
  • 43.
    Return user’s data ClientApp Resource Server Access Token Access Token Get user’s data Access Token Verify
  • 44.
    Return user’s data ClientApp Resource Server Access Token Access Token Get user’s data Access Token Verify
  • 46.
  • 47.
    Client App AuthorizationServer Access Token G enerate
  • 48.
    Issue Access Token ClientApp Authorization Server Access Token G enerate
  • 49.
    Issue Access Token ClientApp Authorization Server Access Token Access Token G enerate
  • 51.
  • 52.
    Client App Resource Server AuthorizationServer Access Token G enerate
  • 53.
    Client App Resource Server IssueAccess Token Authorization Server Access Token G enerate
  • 54.
    Client App Resource Server AccessToken Issue Access Token Authorization Server Access Token G enerate
  • 55.
    Client App Resource Server AccessToken Get user’s data Issue Access Token Authorization Server Access Token G enerate
  • 56.
    Client App Resource Server AccessToken Access Token Get user’s data Issue Access Token Authorization Server Access Token G enerate
  • 57.
    Client App Resource Server AccessToken Access Token Get user’s data Access Token Issue Access Token Authorization Server Access Token G enerate
  • 58.
    Client App Resource Server AccessToken Access Token Get user’s data Access Token Verify Issue Access Token Authorization Server Access Token G enerate
  • 59.
    Return user’s data ClientApp Resource Server Access Token Access Token Get user’s data Access Token Verify Issue Access Token Authorization Server Access Token G enerate
  • 60.
    Return user’s data ClientApp Resource Server Access Token Access Token Get user’s data Access Token Verify Issue Access Token Authorization Server Access Token G enerate
  • 61.
  • 62.
    S i ng l e S i g n - O n
  • 63.
    G o og l e S i g n I n F l o w
  • 64.
    G o og l e S i g n I n F l o w 1. Mobile: Użytkownik klika przycisk Zaloguj z Google
  • 65.
    G o og l e S i g n I n F l o w 1. Mobile: Użytkownik klika przycisk Zaloguj z Google 2. Mobile: Otwieramy formularz logowania Google z parametrem requestIdToken(serverClientId) żeby dostać ID Token
  • 66.
    G o og l e S i g n I n F l o w 1. Mobile: Użytkownik klika przycisk Zaloguj z Google 2. Mobile: Otwieramy formularz logowania Google z parametrem requestIdToken(serverClientId) żeby dostać ID Token 3. Mobile: User poprawnie loguje się profilem Google
  • 67.
    G o og l e S i g n I n F l o w 1. Mobile: Użytkownik klika przycisk Zaloguj z Google 2. Mobile: Otwieramy formularz logowania Google z parametrem requestIdToken(serverClientId) żeby dostać ID Token 3. Mobile: User poprawnie loguje się profilem Google 4. Mobile: W odpowiedzi na poprawne zalogowanie otrzymujemy ID Token, który wysyłamy do API
  • 68.
    G o og l e S i g n I n F l o w 1. Mobile: Użytkownik klika przycisk Zaloguj z Google 2. Mobile: Otwieramy formularz logowania Google z parametrem requestIdToken(serverClientId) żeby dostać ID Token 3. Mobile: User poprawnie loguje się profilem Google 4. Mobile: W odpowiedzi na poprawne zalogowanie otrzymujemy ID Token, który wysyłamy do API 5. Backend: Odbiera ID Token i sprawdza jego poprawność
  • 69.
    G o og l e S i g n I n F l o w 1. Mobile: Użytkownik klika przycisk Zaloguj z Google 2. Mobile: Otwieramy formularz logowania Google z parametrem requestIdToken(serverClientId) żeby dostać ID Token 3. Mobile: User poprawnie loguje się profilem Google 4. Mobile: W odpowiedzi na poprawne zalogowanie otrzymujemy ID Token, który wysyłamy do API 5. Backend: Odbiera ID Token i sprawdza jego poprawność 6. Backend: Jeśli token jest poprawny to można z niego wyciągnąć dane o użytkowniku i na tej podstawie stworzyć konto/albo nową sesję
  • 70.
    G o og l e S i g n I n F l o w 1. Mobile: Użytkownik klika przycisk Zaloguj z Google 2. Mobile: Otwieramy formularz logowania Google z parametrem requestIdToken(serverClientId) żeby dostać ID Token 3. Mobile: User poprawnie loguje się profilem Google 4. Mobile: W odpowiedzi na poprawne zalogowanie otrzymujemy ID Token, który wysyłamy do API 5. Backend: Odbiera ID Token i sprawdza jego poprawność 6. Backend: Jeśli token jest poprawny to można z niego wyciągnąć dane o użytkowniku i na tej podstawie stworzyć konto/albo nową sesję 7. Mobile: API zwraca odpowiedź, że logowanie jest poprawne lub niepoprawne (+ tokeny refresh/access)
  • 71.
    F a ce b o o k S i g n I n F l o w
  • 72.
    F a ce b o o k S i g n I n F l o w 1. Mobile: Użytkownik klika przycisk Zaloguj z Facebook
  • 73.
    F a ce b o o k S i g n I n F l o w 1. Mobile: Użytkownik klika przycisk Zaloguj z Facebook 2. Mobile: Otwieramy formularz logowania Facebook z Facebook SDK (w zależności od tego czy jest zainstalowana aplikacja FB to będzie formularz natywny, jeśli nie jest zainstalowana to PopUp z WebView)
  • 74.
    F a ce b o o k S i g n I n F l o w 1. Mobile: Użytkownik klika przycisk Zaloguj z Facebook 2. Mobile: Otwieramy formularz logowania Facebook z Facebook SDK (w zależności od tego czy jest zainstalowana aplikacja FB to będzie formularz natywny, jeśli nie jest zainstalowana to PopUp z WebView) 3. Mobile: User poprawnie loguje się profilem Facebook
  • 75.
    F a ce b o o k S i g n I n F l o w 1. Mobile: Użytkownik klika przycisk Zaloguj z Facebook 2. Mobile: Otwieramy formularz logowania Facebook z Facebook SDK (w zależności od tego czy jest zainstalowana aplikacja FB to będzie formularz natywny, jeśli nie jest zainstalowana to PopUp z WebView) 3. Mobile: User poprawnie loguje się profilem Facebook 4. Mobile: W odpowiedzi na poprawne zalogowanie otrzymujemy AccessToken, który wysyłamy do API
  • 76.
    F a ce b o o k S i g n I n F l o w 1. Mobile: Użytkownik klika przycisk Zaloguj z Facebook 2. Mobile: Otwieramy formularz logowania Facebook z Facebook SDK (w zależności od tego czy jest zainstalowana aplikacja FB to będzie formularz natywny, jeśli nie jest zainstalowana to PopUp z WebView) 3. Mobile: User poprawnie loguje się profilem Facebook 4. Mobile: W odpowiedzi na poprawne zalogowanie otrzymujemy AccessToken, który wysyłamy do API 5. Backend: Odbiera AccessToken, sprawdza jego poprawność z wykorzystaniem endpointu /user z parametrem me jako user-id (czyli /v3.2/me) oraz dla bezpieczeństwa parametrem appsecret_proof
  • 77.
    F a ce b o o k S i g n I n F l o w 1. Mobile: Użytkownik klika przycisk Zaloguj z Facebook 2. Mobile: Otwieramy formularz logowania Facebook z Facebook SDK (w zależności od tego czy jest zainstalowana aplikacja FB to będzie formularz natywny, jeśli nie jest zainstalowana to PopUp z WebView) 3. Mobile: User poprawnie loguje się profilem Facebook 4. Mobile: W odpowiedzi na poprawne zalogowanie otrzymujemy AccessToken, który wysyłamy do API 5. Backend: Odbiera AccessToken, sprawdza jego poprawność z wykorzystaniem endpointu /user z parametrem me jako user-id (czyli /v3.2/me) oraz dla bezpieczeństwa parametrem appsecret_proof 6. Backend: Jeśli token jest poprawny to można z niego wyciągnąć dane o użytkowniku i na tej podstawie stworzyć konto/albo nową sesję.
  • 78.
    F a ce b o o k S i g n I n F l o w 1. Mobile: Użytkownik klika przycisk Zaloguj z Facebook 2. Mobile: Otwieramy formularz logowania Facebook z Facebook SDK (w zależności od tego czy jest zainstalowana aplikacja FB to będzie formularz natywny, jeśli nie jest zainstalowana to PopUp z WebView) 3. Mobile: User poprawnie loguje się profilem Facebook 4. Mobile: W odpowiedzi na poprawne zalogowanie otrzymujemy AccessToken, który wysyłamy do API 5. Backend: Odbiera AccessToken, sprawdza jego poprawność z wykorzystaniem endpointu /user z parametrem me jako user-id (czyli /v3.2/me) oraz dla bezpieczeństwa parametrem appsecret_proof 6. Backend: Jeśli token jest poprawny to można z niego wyciągnąć dane o użytkowniku i na tej podstawie stworzyć konto/albo nową sesję. 7. Mobile: Otrzymujemy odpowiedź, że logowanie jest poprawne lub niepoprawne (+ tokeny refresh/access)
  • 79.
    U s er S e r v i c e
  • 80.
    $ composer requirelexik/jwt-authentication-bundle
  • 81.
  • 82.
  • 83.
    security: providers: in_memory: memory: users: admin: # Testdummy user password: password roles: 'ROLE_ADMIN' encoders: SymfonyComponentSecurityCoreUserUser: plaintext firewalls: login: pattern: ^/api/login stateless: true anonymous: true json_login: check_path: /api/login_check success_handler: lexik_jwt_authentication.handler.authentication_success failure_handler: lexik_jwt_authentication.handler.authentication_failure provider: in_memory api: pattern: ^/api stateless: true guard: authenticators: [lexik_jwt_authentication.jwt_token_authenticator] access_control: - { path: ^/api/login, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
  • 84.
    curl -X POST https://example.com/api/login_check -H 'Content-Type: application/json' -d '{"username":"admin","password":"password"}'
  • 85.
    { "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE1NjY0NjgzODQsImV4cCI6MTU2N jQ3MTk4NCwicm9sZXMiOlsiUk9MRV9BRE1JTiJdLCJ1c2VybmFtZSI6ImFkbWluIn0.TJST6ZifM4jIZiuejvMkd0PZ vwZWlgS6MKsloNBEUHmYTTGyZlcsJx-ol04U-cn2uAzisfv-oHLw7I7k1sqVY5V28RjUg-OBN-CtEzEAh_OL-a- kweuO0q4KCsv5Pm1R77rahfZ2rZ1Z0j3A6SmOTqEjpVBIOBHO76Gy6hGY7uHiyVgi4hfxhBUV- Pd1zBxule77upXX9AgWwFnvltbSD3GioUTIlu8k5kizX7_IteZAhXRs7-o0jE3oMPQi0TCQbp7wf_-bSfGmtV- XoPnxT- jmvWPGGjAI50_BEgwif8vwXPEn7gHN4nzwrOJd9YjVfn_9l8bsmx_ruqXX0C_FMUlDkcABDsApNJzo3dpeAnEdMYPKo _jnVMfaV0QcBPrcfBTvldCDW67WaMDC7T9kNUAj4weB48rnjDzveyg6ymzoLanMTMq567pFBWfiVRrQluQ0QKnTtXQD -qbg4k8wuTlNxZvd1zJ2HPa7U3gH9ZVrwK27P3_7hZzZghPFlhNh- J_vm7nVd0H4EVIlFj6mtPjnCiXyRugM4g8HYEc_Kx_UnbL4EBkWPpzrT2X9tYQvXnWtyxeWNubHGoizPlxYOAoIZeGT Lp0HKKSecvvrZLNjea_GP_Fo6KOCC6N4o4cy2FsFdM06Zk6pC_0R-xzOUtqc3-dBkdBrhTyVFc3gbgU" }
  • 86.
  • 87.
    S y mf o n y G u a r d
  • 88.
    $ composer requiresymfony/security-guard
  • 89.
    interface AuthenticatorInterface { public functionsupports(Request $request): bool; public function getCredentials(Request $request); public function getUser($credentials, UserProviderInterface $userProvider): ?UserInterface; public function checkCredentials($credentials, UserInterface $user): bool; public function createAuthenticatedToken(UserInterface $user, $providerKey): GuardTokenInterface; public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response; public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey): ?Response; public function supportsRememberMe(): bool; }
  • 90.
    $ composer requireleague/oauth2-google
  • 91.
    class GoogleAuthenticator implementsAuthenticatorInterface { … }
  • 92.
    public function supportsRememberMe():bool { return true; } public function checkCredentials($credentials, UserInterface $user): bool { return true; } public function supports(Request $request): bool { return $request->attributes->get('_route') === 'api_login_check'; }
  • 93.
    public function getUser($credentials,UserProviderInterface $userProvider): UserInterface { $googleUser = $this->getClient()->fetchUserFromCredentials($credentials); $existingUser = $this->repository->findOneBy(['googleId' => $googleUser->getId()]); if ($existingUser) { return $existingUser; } $user = $this->createUser($googleUser); $this->em->persist($user); $this->em->flush(); return $user; }
  • 94.
    protected function createUser(GoogleUser$googleUser): User { $user = new User(); $user->setEmail($googleUser->getEmail()); $user->setGoogleEmail($googleUser->getEmail()); $user->setGoogleId($googleUser->getId()); $user->setFirstName($googleUser->getFirstName()); $user->setLastName($googleUser->getLastName()); return $user; }
  • 95.
    protected function getClient():GoogleClient { return $this->clients['google']; } public function getCredentials(Request $request): array { return $this->getClient()->getCredentialsFromIdToken($request->query->get('id_token', '')); } protected function fetchUserFromCredentials(array $credentials): GoogleUser { return $this->getClient()->fetchUserFromCredentials($credentials); }
  • 96.
    public function onAuthenticationSuccess(Request$request, TokenInterface $token, $providerKey): ?Response { // LexikBundleJWTAuthenticationBundleSecurityHttpAuthenticationAuthenticationSuccessHandler return $this->authenticationSuccessHandler->onAuthenticationSuccess($request, $token); } public function onAuthenticationFailure(Request $request, AuthenticationException $authException): ?Response { // LexikBundleJWTAuthenticationBundleSecurityHttpAuthenticationAuthenticationFailureHandler return $this->authenticationFailureHandler->onAuthenticationFailure($request, $authException); }
  • 97.
    public function start(Request$request, AuthenticationException $authException = null): Response { $ex = $authException ?? new AuthenticationException('Unknown error'); return $this->authenticationFailureHandler->onAuthenticationFailure($request, $ex); }
  • 98.
    use LeagueOAuth2ClientProviderExceptionIdentityProviderException; use LeagueOAuth2ClientProviderGoogle; useLeagueOAuth2ClientProviderGoogleUser; class GoogleClient { public function __construct(string $clientId, string $clientSecret) { $this->provider = new Google([ 'clientId' => $clientId, 'clientSecret' => $clientSecret, ]); $this->clientId = $clientId; } public function getCredentialsFromIdToken(string $idToken): ?array { $request = $this->provider->getRequest('GET', 'https://oauth2.googleapis.com/tokeninfo?id_token=' . $idToken); $response = $this->provider->getParsedResponse($request); return $this->validateIdTokenResponse($response) ? $response : null; } private function validateIdTokenResponse(array $response): bool { return preg_match('/^(?:https://)?accounts.google.com$/', $response['iss'] ?? '') === 1 && ($response['aud'] ?? '') === $this->clientId && ($response['exp'] ?? 0) > time(); } public function fetchUserFromCredentials($credentials): GoogleUser { return new GoogleUser($credentials); } }
  • 99.
    AppSecurityAuthenticatorGoogleAuthenticator: arguments: - { google:'@AppClientGoogleClient' } AppClientGoogleClient: arguments: - '%env(OAUTH_GOOGLE_ID)%' - '%env(OAUTH_GOOGLE_SECRET)%'
  • 100.
    $ composer requireleague/oauth2-facebook
  • 101.
    class FacebookAuthenticator extendsAbstractSocialAuthenticator { public function getCredentials(Request $request): FacebookUser {…} public function getUser($credentials, UserProviderInterface $userProvider): UserInterface {…} protected function getClient(): FacebookClient {…} protected function createUser(FacebookUser $facebookUser): User {…} protected function getService(): string {…} }
  • 102.
    curl -X POST https://example.com/api/login_check?service=google&id_token=token curl -X POST https://example.com/api/login_check?service=facebook&id_token=token
  • 103.
    E m ai l / P a s s w o r d L o g I n
  • 104.
    security: firewalls: base_login: pattern: ^/api/base_login stateless: true anonymous:true provider: in_memory form_login: check_path: /api/base_login/login_check success_handler: lexik_jwt_authentication.handler.authentication_success failure_handler: lexik_jwt_authentication.handler.authentication_failure require_previous_session: false access_control: - { path: ^/api/base_login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
  • 105.
  • 106.
    curl -X POST https://example.com/api/base_login/login_check -H 'Content-Type: application/x-www-form-urlencoded' -d '_username=admin&_password=password'
  • 107.
    R e fr e s h T o k e n s
  • 108.
    $ composer requiregesdinet/jwt-refresh-token-bundle
  • 109.
    gesdinet_jwt_refresh_token: ttl: 2592000 #30days ttl_update: true user_identity_field: username
  • 110.
    gesdinet_jwt_refresh_token: path: /api/token/refresh defaults: {_controller: gesdinet.jwtrefreshtoken:refresh }
  • 111.
    security: firewalls: refresh: pattern: ^/api/token/refresh stateless: true anonymous:true access_control: - { path: ^/api/token/refresh, roles: IS_AUTHENTICATED_ANONYMOUSLY }
  • 112.
    G d zi e t e m i k r o s e r w i s y ?
  • 115.
    P Y TAN I A ?
  • 116.