Closed killua-eu closed 3 years ago
Probably the default server response mode is fragment
, you should ask for parameters in query string when the IdP redirectes to your app, because fragment can't be read on the server side.
So you auth request should include response_mode=query
:
$redirectAuthorizationUri = $authorizationService->getAuthorizationUri(
$client,
['response_mode' => 'query'] // include it
);
Try this and let me know.
Yep, this helped a bit :) Is there a complete login / refresh / logout example somewhere? I crammed all the code below under a single path
public function keycloak($request, $response) {
$oid['server'] = 'https://id.lxd';
$oid['realm'] = 't1';
$oid['user']['name'] = 'x';
$oid['user']['pass'] = 'x';
$oid['client']['id'] = 'new-client';
$oid['client']['secret'] = '*********';
$oid['uri']['base'] = $oid['server'] . '/auth';
$oid['uri']['realm'] = $oid['uri']['base'] . '/realms/' . $oid['realm'];
$oid['uri']['admin'] = $oid['uri']['base'] . '/admin/realms/' . $oid['realm'];
$oid['uri']['authorization'] = $oid['uri']['realm'] . '/protocol/openid-connect/auth';
$oid['uri']['accesstoken'] = $oid['uri']['realm'] . '/protocol/openid-connect/token';
$oid['uri']['userinfo'] = $oid['uri']['realm'] . '/protocol/openid-connect/userinfo';
$oid['uri']['logout'] = $oid['uri']['realm'] . '/protocol/openid-connect/logout';
$oid['uri']['discovery'] = $oid['uri']['realm'];
$oid['uri']['jwks'] = $oid['uri']['realm'] . '/protocol/openid-connect/certs';
$issuer = (new IssuerBuilder())->build($oid['uri']['discovery']);
// print("<pre>".print_r($issuer,true)."</pre>");
$clientMetadata = ClientMetadata::fromArray([
'client_id' => $oid['client']['id'],
'client_secret' => $oid['client']['secret'],
'token_endpoint_auth_method' => 'client_secret_basic', // the auth method to the token endpoint
'redirect_uris' => [
'https://glued.lxd/keycloak-login',
],
]);
//print("<pre>".print_r($clientMetadata,true)."</pre>");
$client = (new ClientBuilder())
->setIssuer($issuer)
->setClientMetadata($clientMetadata)
->build();
//print("<pre>".print_r($client,true)."</pre>");
// Authorization
$authorizationService = (new AuthorizationServiceBuilder())->build();
//print("<pre>".print_r($authorizationService,true)."</pre>");
$redirectAuthorizationUri = $authorizationService->getAuthorizationUri(
$client,
['response_mode' => 'query'] // include it
);
print("<pre>".print_r($redirectAuthorizationUri,true)."</pre>");
$redirectauthorizationService = (new AuthorizationServiceBuilder())->build();
//print("<pre>".print_r($redirectauthorizationService,true)."</pre>");
// you can use this uri to redirect the user
header('Location: '.$redirectAuthorizationUri);
// Get access token
/** @var ServerRequestInterface::class $serverRequest */
$serverRequest = $request; // get your server request
$callbackParams = $authorizationService->getCallbackParams($serverRequest, $client);
$tokenSet = $authorizationService->callback($client, $callbackParams);
$idToken = $tokenSet->getIdToken(); // Unencrypted id_token
$accessToken = $tokenSet->getAccessToken(); // Access token
$claims = $tokenSet->claims(); // IdToken claims (if id_token is available)
$refreshToken = $tokenSet->getRefreshToken(); // Refresh token
// print_r($callbackParams);
//print_r($tokenSet);
print_r($claims);
//print_r($idToken);
//rint_r($accessToken);
die('dieing a bit');
// Refresh token
$tokenSet = $authorizationService->refresh($client, $tokenSet->getRefreshToken());
// Get user info
$userInfoService = (new UserInfoServiceBuilder())->build();
$userInfo = $userInfoService->getUserInfo($client, $tokenSet);
}
When I die()
, I get redirected to the auth page and I get to print the $claims
. Uncommenting the refresh token part fails with
Argument 2 passed to Facile\OpenIDClient\Service\AuthorizationService::refresh() must be of the type string, null given, called in /srv/varwwwhtml/oidtest/Core/Controllers/AuthController.php on line 158
You can't have those value at that time, I suggest you to read something on how OpenID Connect works.
You need to pages, one to redirect the user to the login page, and another one where Keycloak will redirect your user after the login with the code
to request an access code.
So, when you have your configured services, you should have two functions:
Login page:
function login(AuthorizationService $authorizationService, Client $client)
{
$redirectAuthorizationUri = $authorizationService->getAuthorizationUri(
$client,
['response_mode' => 'query']
);
header('Location: ' . $redirectAuthorizationUri);
exit;
}
The user will be redirect to the SSO login page, and if he's not logged in, the SSO will ask him to login, then the user will be redirect to your redirect uri (callback) of your app:
Redirect page:
function callback(AuthorizationService $authorizationService, Client $client, ServerRequestInterface $request)
{
$callbackParams = $authorizationService->getCallbackParams($request, $client);
$tokenSet = $authorizationService->callback($client, $callbackParams);
$idToken = $tokenSet->getIdToken(); // Unencrypted id_token
$accessToken = $tokenSet->getAccessToken(); // Access token
$claims = $tokenSet->claims(); // IdToken claims (if id_token is available)
$refreshToken = $tokenSet->getRefreshToken(); // Refresh token
// All the previous variables should be populated now
}
This is where the SSO redirect the user including che callback params. Now you have your code
to request the access token with a server-to-server request to the SSO.
Hey @thomasvargiu , my bad, I should not attempt to do anything meaningful late in the night. I split up things accordingly. I'm getting an exception The following claims are mandatory: at_hash. (0)
(that bubbles up as Facile \ JoseVerifier \ Exception \ InvalidTokenException, Invalid token provided
). the at_hash really isn't issued by keycloak in the refresh token. The only relevant info I found concerning this was https://stackoverflow.com/questions/60818373/configure-keycloak-to-include-an-at-hash-claim-in-the-id-token ... not sure at which point/how to insert the response_type=id_token token
... when I slipped this into
$redirectAuthorizationUri = $authorizationService->getAuthorizationUri(
$this->oidc, ['response_type'=>'id_token token']);
, I got the nonce MUST be provided for implicit and hybrid flows
exception. Using ['response_type'=>'id_token token use_nonce']
didn't help. I'm at loss now again.
To be specific, the at_hash exception comes with $tokenSet = $authorizationService->refresh($client, $refreshToken);
Ok, right.
Try this:
$redirectAuthorizationUri = $authorizationService->getAuthorizationUri($this->oidc, [
'response_mode' => 'query'
'response_type' => 'code id_token token'
]);
With this parameters you should have all you want and it should work. But it depends on what you need.
If you require id_token
in the response_type
the Identity Provider (IdP) will provide you an id_token
and you can use $tokenSet->claims()
to read the user infos in the id_token
JWT without to ask to the UserInfo
service.
If you only want to authenticate an user (and then maybe open a session) you just need code id_token
. code
is to enable the code authorization flow (the default one and one of the most secure flow), id_token
is to receive the JWT with the user info without to request these infos from another service (like Keyclock itself).
Requiring token
in the response_type
you're asking for an access token (and refresh token).
Hey,
I retested the 0.2 release. It seems I get lost along the lines that get the access token.
Any ideas why this might be failing? Working against keycloak 12.04