aurelia-authentication OAuth2 response state value differs - authentication

I'm attempting an implementation of aurelia-authentication with an OIDC provider (IdentityServer4) and seem to be running into an issue with logging a user out.
The short of it is I'm not able to logout users successfully using the authService.logout function mentioned in the OIDC configuration section (https://aurelia-authentication.spoonx.org/oidc.html).
In looking into it a bit further I've tracked it down to a promise rejection in the logout function which provides the message: "OAuth2 response state value differs"
if (logoutResponse.state !== stateValue) {
return Promise.reject('OAuth2 response state value differs');
}
logoutReponse seems to be the culprit as it's coming through as an object with the state property named incorrectly {/login?state: "qAIxYwKqLHYJtyar2PfdvaROWT1O56P7"}.
I can actually change the if statement to:
if (logoutResponse['/login?state'] !== stateValue) {
return Promise.reject('OAuth2 response state value differs');
}
which seems to be working fine, but requires us to modify the aurelia-authentication source directly.
Any thoughts from anyone as to why the "state" property is coming through as a relative path instead of just "state"?

So after spending more time on this I was able to track the issue down and find a solution.
The solution was to change the aurelia-authentication authConfig postLogoutRedirectUri value to just the root page (http://localhost:8080). Additionally, I needed to define that URI under the PostLogoutRedirectUris within my IdentityServer4 Client definition.
logoutResponse was then coming through correctly with a property named state property that holds the correct value and permits the redirect successfully.

Related

Error when checking for custom claims property on rules auth token

I receive an error in the emulator when I try to check for a custom claim field that does not exist on the request.auth.token object when checking storage.rules. The request fails which is correct if the property is missing but I am concerned about the error.
function isPlatformVerified() {
return request.auth.token.platformVerified == 'ok';
}
and this is the error shown in the emulator:
com.google.firebase.rules.runtime.common.EvaluationException: Error: /Users/marcel/git/dev/storage.rules line [68], column [14]. Property platformVerified is undefined on object.
I wish to check if the custom claims has this property and if it has that it contains the correct value. How do I do this without getting an error (or can I ignore this??)
Many thanks
Most likely the custom claim hasn't propagated to the client and rules yet.
Custom claims are part of the token that the client sends with its requests, and that token is only auto-refreshed once per hour. So it may take up to an hour before a new claim shows up in the client, and thus in the security rules.
You can force a refresh of the ID token on the client by calling user.reload(), to ensure that the new claims will be present before the auto-refresh.

How to make CORS API call from Blazor client app with authentication using AutoRest Client?

I am trying to call Web API from Blazor Client App. The API sends required CORS headers and works fine when I call the API using plain Javascript.
The API needs Auth cookies to be included when making a call so using JavaScript I can call:
fetch(uri, { credentials: 'include' })
.then(response => response.json())
.then(data => { console.log(data) })
.catch(error => console.log('Failed'));
Now, I am trying to do the same on Blazor. I came across this section on the docs which says:
requestMessage.Properties[WebAssemblyHttpMessageHandler.FetchArgs] = new
{
credentials = FetchCredentialsOption.Include
};
When I make a call now, it fails with following exception:
WASM: System.Net.Http.HttpRequestException: TypeError: Failed to execute 'fetch' on 'Window': The provided value '2' is not a valid enum value of type RequestCredentials.
I noticed that adding following on Statrup.cs allows me to call API including credentials (here):
if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("WEBASSEMBLY")))
{
WebAssemblyHttpMessageHandler.DefaultCredentials = FetchCredentialsOption.Include;
}
Now, I would like to call the API using AutoRest generated API Client so that I can reuse existing client and save lot of time. Setting DefaultCredentials as above doesn't work and shows following exception:
WASM: Microsoft.Rest.HttpOperationException: Operation returned an invalid status code 'InternalServerError'
Setting the requestMessage.Properties as above, says
The provided value '2' is not a valid enum value of type RequestCredentials`.
I am already injecting HttpClient from Blazor using this technique.
This is not really the answer... I just need space
Setting the requestMessage.Properties as above, says The provided
value '2' is not a valid enum value of type RequestCredentials
If so, what is wrong with the other method I suggested, which I guess is working.
Incidentally,
The provided value '2' is not a valid enum value of type
RequestCredentials
is not related to Blazor, right ? No such type (RequestCredentials) in Blazor. Perhaps your code, whatever it may be, gets the numeric value of the FetchCredentialsOption.Include and not its Enum string
Consider instantiating an HttpRequestMessage object, and configuring it according to your requirements.
Hope this helps...

intermittent error from rally 'Not authorized to perform action: Invalid key' for POST request in chrome extension

I developed a chrome extension using Rally's WSAPI v2.0, and it basically does the following things:
get user and project, and store them
get current iteration everytime
send a post request to create a workitem
For the THIRD step, I sometimes get error ["Not authorized to perform action: Invalid key"] since end of last month.
[updated]Error can be reproduced everytime if I log in Rally website via SSO before using the extension to send requests via apikey.
What's the best practice to send subsequent requests via apikey in my extension since I can't control end users' habits?
I did see some similar posts but none of them is helpful... and in case it helps:
I'm adding ZSESSIONID:apikey in my request header, instead of user /
password to authenticate, so I believe no security token is needed
(https://comm.support.ca.com/kb/api-key-and-oauth-client-faq/kb000011568)
url starts with https://rally1.rallydev.com/slm/webservice/v2.0/
issue is fixed after clearing cookies for
https://rally1.rallydev.com/, but somehow it appears again some time
later
I checked the cookie when the issue was reproduced, and found one with name of ZSESSIONID and its value became something else rather than the apikey. Not sure if that matters though...
code for request:
function initXHR(method, url, apikey, cbFunc) {
let httpRequest = new XMLHttpRequest();
...
httpRequest.open(method, url);
httpRequest.setRequestHeader('Content-Type', ' application\/json');
httpRequest.setRequestHeader('Accept', ' application\/json');
httpRequest.setRequestHeader('ZSESSIONID', apikey);
httpRequest.onreadystatechange = function() {
...
};
return httpRequest;
}
...
usReq = initXHR ('POST', baseURL+'hierarchicalrequirement/create', apikey, function(){...});
Anyone has any idea / suggestion? Thanks a million!
I've seen this error when the API key had both read-only and full-access grants configured. I would start by making sure your key only has the full-access grant.

View model with the authorization example

I want to restrict access to view models according to the authorization or JWT. I found examples for the read models, but how to implement it for the view models in the right way?
In resolve framework every view-model can have own serializer and deserializer. These functions are used for view-models which have non-trivial state object, which cannot be automatically serialized by JSON.stringify and be restored within JSON.parse - for example, it's useful for Immutable.JS or seamless-immutable.
In fact serializer has two arguments - first is state object for serialization, and second argument is JWT token from invoker. Since view-model is always had been invoked from current client, either HTTP request or API handler, JWT token is always present and can be used for access restriction
const serializeState = (state, jwtToken) => {
if(jwtToken != null && !isGoodToken(jwtToken)) { // JWT token is present and custom validation failed
throw new Error('Access denied')
}
return JSON.stringify(state) // Or custom serialize procedure
}
export default serializeState
Important notice: do not restrict serialized state access in case of jwtToken absence, since it used for internal purposes in snapshot adapters. Always allow return serialized state if second argument is undefined. Else if jwtToken present and invalid, error can be thrown to restrict access.

Google OpenIDConnect: Why am I not getting an 'openid_id' value along with 'sub'?

I've read all the documentation I can find on migrating from Google OpenID 2 to OAuth 2/OpenIDConnect, and am currently using a nice class from phpclasses.org . This class seems to work quite well with both Google and Facebook (haven't yet tried other providers), but I'm having a problem with just one aspect of Google's migration path that is quite critical to me: obtaining the google user's old OpenID identifier in addition to the new OpenIDConnect 'sub' value for that user. I've got users registered in my database only through their old OpenID identifiers.
According to Step 3 in Google's Migration Guide it looks like all I should need to do is add a parameter "openid.realm=http://www.example.com" to the authentication request sent to https://accounts.google.com/o/oauth2/auth.
I looked up in my old code what the realm was that I used for its OpenID registration process (it was 'http://' . $_SERVER['HTTP_HOST'];), and then I made sure that the redirect urls in my application were compatible with that realm.
I added that value (url-encoded) as the value of an openid.realm parameter passed on the authentication request made within the class. But when the class exchanged the token for an access token, it got back the correct email, name, sub, etc, but there was no openid_id parameter present. BTW, my scope parameter is 'openid email profile'
Does anyone have a suggestion for what else I should try, or what I can do to determine what the problem is? Does anyone have successful experience getting the openid_id parameter value in php code? I'd really rather not go the client-side route with their "Sign-in with Google" button, and according to the docs that really shouldn't be necessary (plus there's no particular reason to believe it would solve my problem if I did it).
Just discovered it's in the id_token returned along with the access_token when you exchange the authorization_code for the access_token.
In the Migration Document, Step 3 first two paragraphs:
When you send an OpenID Connect authentication request URI to Google
as described in Step 1, you include an openid.realm parameter. The
response that is sent to your redirect_uri includes an authorization
code that your application can use to retrieve an access token and an
ID token. (You can also retrieve an ID token directly from the OpenID
Connect authentication request by adding id_token to the response_type
parameter, potentially saving a back-end call to the token endpoint.)
The response from that token request includes the usual fields
(access_token, etc.), plus an openid_id field and the standard OpenID
Connect sub field. The fields you need in this context are openid_id
and sub:
This is confusing and misleading/wrong. What token request? The authentication request returns an authorization code that you can exchange for an access_token and an id_token. The parenthetical remark about adding id_token to the response_type doesn't help much, as the various ways I tried to do that resulted in an error. But in any event, the
"usual fields (access_token, etc.), plus an openid_id field..."
is wrong. The access_token never appears in the same list at the openid_id field. The access_token appears in a list with the id_token, and the openid_id field is encoded within the id_token!
For testing purposes, you can decode an id_token using https://www.googleapis.com/oauth2/v1/tokeninfo?id_token=<string>
In this documentation I couldn't find a useful description for how to decode an id_token, only caveats about their being sensitive, and how to validate them (though validation is not needed if obtained directly from a google endpoint as is the case here). I downloaded google's php client, and extracted code from it (src/Google/Auth/OAuth2.php and src/Google/Utils.php). And from that it's easy enough to figure out how to decode the id_token string: explode on ., base64_decode element 1, and json_decode that.
Update 2015-05-21: In reply to #Arthur's "answer", which would have been more appropriate as a comment on this answer. I would have commented on that answer myself, but comments aren't allowed to be very long and don't allow image uploads, plus I thought this extra info improves my answer...
Below is a screenshot from netbeans/xdebug, showing the array elements I get when decoding the id_token I get. Interesting that the intersection of the fields listed here with the fields listed by #Arthur is the null set. So I suspect that whatever #Arthur is decoding, it is not an id_token of the kind described here. I'm not familiar enough with this stuff even to guess what it is that's being decoded in that answer.
I'm afraid I don't have the time to dig through the library I use to extract the exact code path that produces the id_token I decoded to get this array using the simple algorithm I described. But I can tell you that the library I use is this: http://www.phpclasses.org/package/7700-PHP-Authorize-and-access-APIs-using-OAuth.html
Using it just as documented does not give you the id_token you need for this for two reasons:
The pre-configured server for Google with Oauth 2 doesn't handle the openid.realm parameter. To handle that, I added the following server definition to the oauth_configuration.json file:
"Google-OpenIdConnect":
{
"oauth_version": "2.0",
"dialog_url": "https://accounts.google.com/o/oauth2/auth?response_type=code&client_id={CLIENT_ID}&redirect_uri={REDIRECT_URI}&scope={SCOPE}&state={STATE}&openid.realm={REALM}",
"offline_dialog_url": "https://accounts.google.com/o/oauth2/auth?response_type=code&client_id={CLIENT_ID}&redirect_uri={REDIRECT_URI}&scope={SCOPE}&state={STATE}&access_type=offline&approval_prompt=force",
"access_token_url": "https://accounts.google.com/o/oauth2/token"
},
Just after the call to Initialize(), you need to add
$client->store_access_token_response = true;
Without that, the actual access_token response is not accessible (at least not the way I'm using the class). With those two changes in place, my exact code to get the openid_id using this class is as follows:
protected function jwt_decode($jwt) {
$segments = explode(".", $jwt);
if (count($segments) != 3) {
throw new Exception("Wrong number of segments in token: $jwt");
}
// Parse envelope.
$envelope = json_decode($this->urlSafeB64Decode($segments[0]), true);
if (!$envelope) {
throw new Exception("Can't parse token envelope: " . $segments[0]);
}
// Parse token
$json_body = $this->urlSafeB64Decode($segments[1]);
$payload = json_decode($json_body, true);
return $payload;
}
protected function getOpenid_id() {
require_once 'Phpclasses/Http/Class.php';
require_once 'Phpclasses/OauthClient/Class.php';
require 'Phpclasses/Google/private/keys.php';
$client = new oauth_client_class;
$client->configuration_file = $phpclasses_oauth_dir . '/oauth_configuration.json';
$client->server = 'Google-OpenIdConnect';
$client->redirect_uri = 'http://' . $_SERVER['HTTP_HOST'] . strtok($_SERVER['REQUEST_URI'], '?');
$client->client_id = $GOOGLE_APPID;
$client->client_secret = $GOOGLE_APPSECRET;
$client->scope = 'openid email';
$client->realm = $this->getRequest()->getScheme() . '://' . $this->getRequest()->getHttpHost();
$me = null;
if (($success = $client->Initialize())) {
// set *after* the call to Initialize
$client->store_access_token_response = true;
if (($success = $client->Process())) {
if (strlen($client->authorization_error)) {
$client->error = $client->authorization_error;
$success = false;
}
elseif (strlen($client->access_token)) {
$success = $client->CallAPI('https://www.googleapis.com/oauth2/v1/userinfo', 'GET', array(), array('FailOnAccessError' => true), $user);
$me = (array) $user;
if (!array_key_exists('id_token', $client->access_token_response)) {
throw new Exception('No id_token in \$client->access_token_response');
}
$openid_id = $this->jwt_decode($client->access_token_response['id_token']);
$me['openid_id'] = $openid_id;
}
}
$success = $client->Finalize($success);
}
if ($client->exit)
exit;
$client->ResetAccessToken();
if ($success) {
return $me;
}
// Code to handle failure...
}
Despite sootsnoot's (own) answer I still can't find the openid_id field anywhere. When decoding the id_token there are only "issuer", "issued_to", "audience", "user_id" , "expires_in" , "issued_at", "email" and "nonce" fields.
No "openid_id" field in sight..
Any ideas?
In response to sootsnoot's response :) And I apologize for not having enough reputation to comment, otherwise would have done so.
Am using an OpenID Connect library that takes endpoints from auto-config: https://accounts.google.com/.well-known/openid-configuration
So assume the endpoints are not the problem. Indeed it seems I was checking the wrong id_token. However, even when checking the correct one I still don't see the "openid_id" field. I now see everything you have, except that I have a "nonce" field instead of the "openid_id" field:
stdClass::__set_state(array( 'iss' => 'https://accounts.google.com', 'sub' => ****, 'azp' => ****, 'email' => ****, 'nonce' => ****, 'at_hash' => ****, 'email_verified' => true, 'aud' => ****, 'iat' => ****, 'exp' => 1432300788, ))
Must be doing something wrong, but what...
Final update:
Found the issue: was passing realm parameter as openid_realm=... instead of openid.realm=...
Oh do I feel stupid... :)