I am using ABCPdf 11 to convert html to pdf, my html page which needs to be converted required JWT token so that needs to be passed to ABCChrome so it can use the JWT token.
I have tried the following but the auth still fails:
doc.HtmlOptions.HttpAdditionalHeaders = $"Authorization: Bearer {accessToken}";
I followed example from here: https://www.websupergoo.com/helppdfnet/default.htm?page=source%2F5-abcpdf%2Fxhtmloptions%2F2-properties%2Fhttpadditionalheaders.htm
From the description in the above URL, I have also tried the below options:
doc.HtmlOptions.NoCookie = true;
doc.HtmlOptions.Media = MediaType.Screen;
After adding HttpAdditionalHeaders and when I get the http status from the pdf library I do get 401 http status code which confirms the
var imageId = doc.AddImageUrl(model.Url);
var status = doc.HtmlOptions.ForChrome.GetHttpStatusCode(imageId);
The status here is 401 - unauthorized
The HttpAdditionalHeaders property is not currently supported by the ABCChrome Engine. The only HtmlOptions supported by ABCChrome are specified here.
There are a few things you could try:
Check whether the target server supports sending the web token via GET request parameters - I guess you've probably done this already :-)
Make the AddImageUrl request URL to an intermediary web server (even a local HttpServer) to a script which can fetch the page for you based on any GET parameters.
If the service you are attempting to access accepts ajax requests you could try using javascript to inject the response into a page using XMLHttpRequest.setRequestHeader(). NB if you use a local file (e.g. file://) for this you may come across some Chromium enforced JavaScript security issues.
I do know that WebSupergoo offer free support for all their licenses, including trial licenses.
Good luck.
Emailed ABCPdf support and unfortunately ABCChrome does not support HttpAdditionalHeaders property so the work around is to download the html ourselves and convert that to PDF, see example below:
var imageId = doc.AddImageHtml(html); // <- html downloaded from auth url
Also don't forget to add paging:
// add all pages to pdf
while (doc.Chainable(imageId))
{
doc.Page = doc.AddPage();
imageId = doc.AddImageToChain(imageId);
}
for (int i = 1; i <= doc.PageCount; i++)
{
doc.PageNumber = i;
doc.Flatten();
}
I wanted to use Azure Key Vault to store key which will be used to create signature for JwtTokens generated by my API.
For Jwt token authentication I use Microsoft.AspNet.Authentication.JwtBearer and i configured it like that:
app.UseJwtBearerAuthentication(options =>
{
options.TokenValidationParameters.IssuerSigningKey = this.tokenAuthKey;
options.TokenValidationParameters.ValidAudience = this.tokenOptions.Audience;
options.TokenValidationParameters.ValidIssuer = this.tokenOptions.Issuer;
options.TokenValidationParameters.ValidateSignature = true;
options.TokenValidationParameters.ValidateLifetime = false;
options.TokenValidationParameters.ClockSkew = TimeSpan.FromMinutes(0);
});
So, as you see, this configuration requires to provide full key with private part (IssureSigningKey property). It works fine when my key is stored unprotected in xml file, somewhere in file system and I'm just loading it during application startup. However, there is a problem when i try to store my key in Azure Key Vault (AKV). Since AKV only allows to withdraw public part of the key, Im struggling to make it all work together.
I found out that TokenValidationParameters class has delegate property that i can use to provide my own validator method which would use AKV encrypt/decrypt methods to determine if signature is valid.
The problem is I can't determine how to specify my own method that would generate signature part of JWT token, so that configuration of Microsoft.AspNet.Authentication.JwtBearer won't need private key.
My question is: is it possible somehow to specify my own method that creates signature for generated JWT token in JwtBearer configuration or during creation of token using JwtSecurityTokenHandler.CreateToken?
I am attempting to upload a file to S3 following the examples provided in your documentation and source files. Unfortunately, I'm receiving the following errors when attempting an upload:
[Fine Uploader 5.3.2] Invalid policy document or request headers!
[Fine Uploader 5.3.2] Policy signing failed. Invalid policy document
or request headers!
I found a few posts on here with similar errors, but those solutions didn't help me.
Here is my jQuery:
<script>
$('#fine-uploader').fineUploaderS3({
request: {
endpoint: "http://mybucket.s3.amazonaws.com",
accessKey: "changeme"
},
signature: {
endpoint: "endpoint.php"
},
uploadSuccess: {
endpoint: "success.html"
},
template: 'qq-template'
});
</script>
(Please note that I changed the keys/bucket names for security sake.)
I used your endpoint-cors.php as a model and have included the portions that I modified here:
require 'assets/aws/aws-autoloader.php';
use Aws\S3\S3Client;
// These assume you have the associated AWS keys stored in
// the associated system environment variables
$clientPrivateKey = $_ENV['changeme'];
// These two keys are only needed if the delete file feature is enabled
// or if you are, for example, confirming the file size in a successEndpoint
// handler via S3's SDK, as we are doing in this example.
$serverPublicKey = $_ENV['AWS_SERVER_PUBLIC_KEY'];
$serverPrivateKey = $_ENV['AWS_SERVER_PRIVATE_KEY'];
// The following variables are used when validating the policy document
// sent by the uploader.
$expectedBucketName = $_ENV['mybucket'];
// $expectedMaxSize is the value you set the sizeLimit property of the
// validation option. We assume it is `null` here. If you are performing
// validation, then change this to match the integer value you specified
// otherwise your policy document will be invalid.
// http://docs.fineuploader.com/branch/develop/api/options.html#validation-option
$expectedMaxSize = (isset($_ENV['S3_MAX_FILE_SIZE']) ? $_ENV['S3_MAX_FILE_SIZE'] : null);
I also changed this:
// Only needed in cross-origin setups
function handleCorsRequest() {
// If you are relying on CORS, you will need to adjust the allowed domain here.
header('Access-Control-Allow-Origin: http://test.mydomain.com');
}
The POST seems to work:
POST http://test.mydomain.com/somepath/endpoint.php 200 OK
318ms
...but that's where the success ends.
I think part of the problem is that I'm not sure what to enter for "clientPrivateKey". Is that my "Secret Access Key" I set up with IAM?
And I'm definitely unclear on where I get the serverPublicKey and serverPrivateKey. Where am I generating a key-pair on the S3? I've combed through the docs, and perhaps I missed it.
Thank you in advance for your assistance!
First off, you are using endpoint-cors.php in a non-CORS environment. Communication between the browser and your endpoint appears to be same-origin, based on the URL of your signature endpoint. Switch to the endpoint.php example.
Regarding your questions about the keys, you should have create two distinct IAM users: one for client-side operations (heavily restricted) and one for server-side operations (an admin user). For each user, you'll have an access key (public) and a secret key (private). You always supply Fine Uploader with your client-side public key, and use your client-side private key to sign requests server-side. To perform other, more restricted operations (such as deleting files), you should use your server user keys.
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... :)
Looked on google and couldn't find anything.
Any good resources to get started designing my backend for a RESTless webapp thats going to rely heavily on API keys.
I know how to write restless webservices etc, just never used API-keys. Generally do people just generate guids for users etc?
Here's how I'm creating API keys for a web service:
string CreateApiKey(int length)
{
var bytes = new byte[length * 2];
using (var rng = new RNGCryptoServiceProvider())
rng.GetBytes(bytes);
var chars = Convert.ToBase64String(bytes)
.Where(char.IsLetterOrDigit)
.Take(length)
.ToArray();
var key = new String(chars);
return key;
}
GUID's are typically not "random" enough and can be easily guessed by the bad-guys.
Take some "random" data like the user's password hash, some random numbers and run the result through sha1 or a similar hash function.
If you want one API key per account, simply add it to the account metadata table. Otherwise use a table linked to the accountIds to store the api keys.
Server side use a cache using the api-key as the key to store temporarily the account metadata so you only need to go to the db once per session.
And of course everything must go over https to avoid that the API key be stolen.
Now if your service is "session" oriented you can consider using a temporary session key so you do not need to expose the API key. Look for public key encryption to investigate this further.