Unable to download a document from google cloud storage - google-app-engine

I am able to upload a document and download the document from google cloud storage for signed url using httpclient in java.But,when i put the same signed url in browser i am unable to download document for the link.I am getting following error
The request signature we calculated does not match the signature you
provided. Check your Google secret key and signing method.`
But when i mark check shared publicly check box in storage browser i am able to download from the generated signed url.But i want to allow a user to download a document from the browser without marking it as shared publicly.
.
I want to get confirm on some confusing part like
For document to get accessible by user who does not have google account after creating a signed url also i have to check shared publicly check box in storage browser?
But i think if the url is signed then it should not be check for shared publicly checkbox and user who does not have google account can access the document?But in my case it is not happening .According to link
https://developers.google.com/storage/docs/accesscontrol#About-CanonicalExtensionHeaders
it talks about Canonicalized_Extension_Headers .So i put in my request header
request.addHeader("x-goog-acl","public-read");
This is my code
// construct URL
String url = "https://storage.googleapis.com/" + bucket + filename +
"?GoogleAccessId=" + GOOGLE_ACCESS_ID +
"&Expires=" + expiration +
"&Signature=" + URLEncoder.encode(signature, "UTF-8");
System.out.println(url);
HttpClient client = new DefaultHttpClient();
HttpPut request = new HttpPut(url);
request.addHeader("Content-Type", contentType);
request.addHeader("x-goog-acl","public-read");// when i put this i get error
request.addHeader("Authorization","OAuth 1/zVNpoQNsOSxZKqOZgckhpQ");
request.setEntity(new ByteArrayEntity(data));
HttpResponse response = client.execute(request);
When i put request.addHeader("x-goog-acl","public-read");i get error
HTTP/1.1 403 Forbidden error .
.But when i remove this line it is uploaded successfully .It seems like i need to set
request.addHeader("x-goog-acl","public-read") to make it publicly accessible but on putting this on my code i am getting error.
.Any suggestion Please?

Finally Solved it.
To run singed url from browser you have to set HTTP header . In https://developers.google.com/storage/docs/accesscontrol#Construct-the-String
Content_Type Optional. If you provide this value the client (browser) must provide this HTTP header set to the same value.There is a word most.
So if you are providing Content_Type for sign string you must provide same Content_Type in browser http header.When i set Content_Type in browser header this error finally solved

this works for me:
set_include_path("../src/" . PATH_SEPARATOR . get_include_path());
require_once 'Google/Client.php';
function signed_storageURL($filename, $bucket, $p12_certificate_path, $access_id, $method = 'GET', $duration = 3600 )
{
$expires = time( ) + $duration*60;
$content_type = ($method == 'PUT') ? 'application/x-www-form-urlencoded' : '';
$to_sign = ($method."\n"."\n".$content_type."\n".$expires."\n".'/'.$bucket.'/'.$filename);
$signature = '';
$signer = new Google_Signer_P12(file_get_contents($p12_certificate_path), 'notasecret');
$signature = $signer->sign($to_sign);
$signature = urlencode( base64_encode( $signature ) );
return ('https://'.$bucket.'.commondatastorage.googleapis.com/'.$filename.'?GoogleAccessId='.$access_id.'&Expires='.$expires.'&Signature='.$signature);
}
$url = signed_storageURL(rawurlencode("áéíóú espaço & test - =.jpg"),'mybucket', 'mykey.p12','myaccount#developer.gserviceaccount.com');
echo ''.$url.'';

Related

dropbox-api get_thumbnail_v2 & get_thumbnail returns question marks(?) inside rhombs ()

I am trying to use DropBox API to get a thumbnail from DropBox and show them on Lightning Web Component in Salesforce, but can not do it because in a response Apex receiving body with black rhombs and question marks inside.
I use standard HTTP method to call
HttpRequest req = new HttpRequest();
req.setHeader('Authorization', 'Bearer sl.validToken');
req.setHeader('Dropbox-API-Arg', '{"resource": {".tag": "path","path": "/folderName/pictureName.jpg"},"format": "jpeg","size": "w64h64","mode": "strict"}');
req.setHeader('Content-Type', 'text/plain; charset=utf-8');
req.setEndpoint('https://content.dropboxapi.com/2/files/get_thumbnail_v2');
req.setMethod('POST');
Http httpreq = new Http();
HttpResponse res = httpreq.send(req);
this is what I receive in body of response in Apex. The same response I have in Postman.
https://i.stack.imgur.com/90yjI.png
This is what I have in DropBox explorer with same values and headers (JSON)
https://i.stack.imgur.com/ytDxv.png
File scope is Read to everyone. SF Remote Site Settings & CSP Trusted Sites are set.
Short update:
I`ve been able to get JSON From header. I did use that piece of code:
List<String> headers = new List<String>(res.getHeaderKeys());
for(String key : headers){
System.debug('key ->>> '+key+' = '+res.getHeader(key));
}
String jsonString = res.getHeader('Dropbox-Api-Result');
System.debug('->>>ddd '+jsonString);
But still do not understand how to use it as a thumbnail in LWC.
Thank you in advance for your help.
The /2/files/get_thumbnail_v2 Dropbox API endpoint is a "content-download" style endpoint, meaning the "response body contains file content, so the result will appear as JSON in the Dropbox-API-Result response header". So, the illegible value you're receiving is the actual bytes of the thumbnail data itself. You're currently attempting to display it as text, but you'll instead need to save and display it as an image to see the thumbnail. Refer to your platform's documentation for information on how to display an image.
For reference, the Dropbox API v2 Explorer is built with knowledge of the different endpoint formats, so in this case it displays the metadata from the Dropbox-API-Result response header, and just offers the file data, in this case the thumbnail data, as a download via a "Download" button.

Unity/Android ServerAuthCode has no idToken on Backend

I have an unity app and use the google-play-games plugin with google *.aar versions 9.4.0. I lately changed my Backend (Google App Engine) from php to java. My problem is the following: in php the serverauthcode is used to get the users data (in JWT format) - it was working fine. So I changed to a Java servlet and I am failing since 2 days to get a valid idtoken. I am able to recieve the server auth code from my app and a valid token response is made by GoogleAuthorizationCodeTokenRequest (see code snippet). Unfortunately it does not contain any idtoken content but a valid auth_token. So I can not get the user id to identifiy the user. When I call tokenResponse.parseIdToken(); it is failing with a NullPointerException.
servlet code (authCode is the serverAuthCode I send from the play-games-plugin inside Unity to my GAE):
// (Receive authCode via HTTPS POST)
// Set path to the Web application client_secret_*.json file you downloaded from the
// Google Developers Console: https://console.developers.google.com/apis/credentials?project=_
// You can also find your Web application client ID and client secret from the
// console and specify them directly when you create the GoogleAuthorizationCodeTokenRequest
// object.
String CLIENT_SECRET_FILE = "/mypath/client_secret.json";
// Exchange auth code for access token
GoogleClientSecrets clientSecrets =
GoogleClientSecrets.load(
JacksonFactory.getDefaultInstance(), new FileReader(CLIENT_SECRET_FILE));
GoogleTokenResponse tokenResponse =
new GoogleAuthorizationCodeTokenRequest(
new NetHttpTransport(),
JacksonFactory.getDefaultInstance(),
clientSecrets.getDetails().getTokenUri(),
clientSecrets.getDetails().getClientId(),
clientSecrets.getDetails().getClientSecret(),
authCode,
REDIRECT_URI) // Specify the same redirect URI that you use with your web
// app. If you don't have a web version of your app, you can
// specify an empty string.
.execute();
String accessToken = tokenResponse.getAccessToken();
// Get profile info from ID token -> HERE IT THROWS AN EXCEPTION.
GoogleIdToken idToken = tokenResponse.parseIdToken();
GoogleIdToken.Payload payload = idToken.getPayload();
String userId = payload.getSubject(); // Use this value as a key to identify a user.
String email = payload.getEmail();
boolean emailVerified = Boolean.valueOf(payload.getEmailVerified());
String name = (String) payload.get("name");
String pictureUrl = (String) payload.get("picture");
String locale = (String) payload.get("locale");
String familyName = (String) payload.get("family_name");
String givenName = (String) payload.get("given_name");
the token response looks like (its invalid now):
{
"access_token" : "ya29.CjA8A7O96w-vX4OCSPm-GMEPGVIEuRTeOxKy_75z6fbYVSXsdi9Ot3NmxlE-j_t-BI",
"expires_in" : 3596,
"token_type" : "Bearer"
}
In my PHP GAE I always had a idToken inside this constuct which contained my encrypted data. But it is missing now?! So I asssume I do somthing differently in Java or I made a mistake creating the new OAuth 2.0 Client on the google console.
I checked the accessToken manually via:
https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=ya29.CjA8A7O96w-vX4OCSPm-GMEPGVIEu-RTeOxKy_75z6fbYVSXsdi9Ot3NmxlE-j_t-BI
{
"issued_to": "48168146---------.apps.googleusercontent.com",
"audience": "48168146---------.apps.googleusercontent.com",
"scope": "https://www.googleapis.com/auth/games_lite",
"expires_in": 879,
"access_type": "offline"
}
Is there something I do not see? Help is very much appreciated...
I found a root cause discussion inside the unity plugin "play-games-services" on github:
https://github.com/playgameservices/play-games-plugin-for-unity/issues/1293
and
https://github.com/playgameservices/play-games-plugin-for-unity/issues/1309
It seems that google switching their authentication flow. In the given links they are talking about adding the email scope inside the plugin to get the idtoken again. I'll try that in the next days and share my experience.
Here is a good explaination about what happens:
http://android-developers.blogspot.de/2016/01/play-games-permissions-are-changing-in.html
If you do what paulsalameh said here (Link to Github) it will work again:
paulsalameh: Sure. After you import the unitypackage, download NativeClient.cs and
PlayGamesClientConfig.cs from my commits (#1295 & #1296), and replace
them in the correct locations.
Afte that "unity play-services-plugin" code update you will be able to add AddOauthScope("email") to PlayGamesClientConfiguration, which allows your server to get the idtoken with the serverAuthCode again...
Code snippet from Unity:
PlayGamesClientConfiguration config = new PlayGamesClientConfiguration.Builder()
.AddOauthScope("email")
.AddOauthScope("profile")
.Build();
Now I am back in business:
{
"access_token" : "ya29.Ci8..7kBR-eBdPw1-P7Pe8QUC7e_Zv7qxCHA",
"expires_in" : 3600,
"id_token" : "eyJhbGciOi......I1NiE0v6kqw",
"refresh_token" : "1/HlSZOo......dQV1y4E",
"token_type" : "Bearer"
}

Accessing authenticated Google Cloud Endpoints API from Google Apps Script

I'm trying to pull some data into a Google sheets spreadsheet from an API that's been built using Google Cloud Endpoints. Here is the API declaration:
#Api(
name = "myendpoint",
namespace =
#ApiNamespace
(
ownerDomain = "mydomain.com",
ownerName = "mydomain.com",
packagePath = "myapp.model"
),
scopes = {SCOPES},
clientIds = {ANDROID_CLIENT_ID, WEB_CLIENT_ID, API_EXPLORER_CLIENT_ID},
audiences = {WEB CLIENT_ID}
)
The method I'm trying to access has authentication enabled by means of the user parameter in the API declaration:
#ApiMethod(name = "ping", httpMethod = HttpMethod.GET, path = "ping")
public StringResponse getPing(User user) throws OAuthRequestException {
CheckPermissions(user);//throws an exception if the user is null or doesn't have the correct permissions
return new StringResponse("pong");
}
This works fine when using the generated client libraries or the gapi js library. However AFAIK I can't use those js libraries in Apps Script.
I've got an OAuth2 flow working using the apps-script-oauth2 library from here, and I'm pretty much using the default setup for creating the service
function getService() {
// Create a new service with the given name. The name will be used when
// persisting the authorized token, so ensure it is unique within the
// scope of the property store.
return OAuth2.createService(SERVICE_NAME)
// Set the endpoint URLs, which are the same for all Google services.
.setAuthorizationBaseUrl('https://accounts.google.com/o/oauth2/auth')
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
// Set the client ID and secret, from the Google Developers Console.
.setClientId(CLIENT_ID)
.setClientSecret(CLIENT_SECRET)
// Set the name of the callback function in the script referenced
// above that should be invoked to complete the OAuth flow.
.setCallbackFunction('ruggedAuthCallback')
// Set the property store where authorized tokens should be persisted.
.setPropertyStore(PropertiesService.getUserProperties())
// Set the scopes to request (space-separated for Google services).
.setScope(SCOPES)
// Below are Google-specific OAuth2 parameters.
// Sets the login hint, which will prevent the account chooser screen
// from being shown to users logged in with multiple accounts.
.setParam('login_hint', Session.getActiveUser().getEmail())
// Requests offline access.
.setParam('access_type', 'offline')
// Forces the approval prompt every time. This is useful for testing,
// but not desirable in a production application.
.setParam('approval_prompt', 'auto')
//.setParam('include_granted_scopes', 'true');
}
These are my methods for accessing the APIs
function getDriveDocs() {
return executeApiMethod('https://www.googleapis.com/drive/v2/','files?maxResults=10');
}
function pingServer(){
return executeApiMethod('https://myapp.appspot.com/_ah/api/myendpoint/v1/','ping');
}
function executeApiMethod(apiUrl, method)
{
//var url = ;
var url = apiUrl + method;
var service = getRuggedService();
return UrlFetchApp.fetch(url, {
'muteHttpExceptions': true,
'method': 'get',
'headers': {
Authorization: 'Bearer ' + service.getAccessToken()
}
});
}
The getDriveDocs() method works perfectly, so I know my auth flow is working correctly. Also, if I call an unauthenticated method in my API I get the correct response. However, when I call the authenticated 'ping' method, the 'user' parameter is always null. Am I missing something in the fetch call? Everything I've read so far seems to suggest that setting
Authorization: 'Bearer ' + service.getAccessToken()
should be enough.
Any help would be much appreciated!
This turned out to be a simple mistake - I had created a new oauth2 credential in the google dev console and had not added the new client id to the API declaration. Here is the working API declaration:
#Api(
name = "myendpoint",
namespace =
#ApiNamespace
(
ownerDomain = "mydomain.com",
ownerName = "mydomain.com",
packagePath = "myapp.model"
),
scopes = {SCOPES},
clientIds = {ANDROID_CLIENT_ID, WEB_CLIENT_ID, API_EXPLORER_CLIENT_ID, GAPPS_CLIENT_ID},
audiences = {WEB CLIENT_ID}
)

Only one auth mechanism allowed; only the X-Amz-Algorithm query parameter..?

I am trying to send a PUT request to an amazonS3 presigned URL. My request seems to be called twice even if I only have one PUT request. The first request returns 200 OK, the second one returns 400 Bad Request.
Here is my code:
var req = {
method: 'PUT',
url: presignedUrl,
headers: {
'Content-Type': 'text/csv'
},
data: <some file in base64 format>
};
$http(req).success(function(result) {
console.log('SUCCESS!');
}).error(function(error) {
console.log('FAILED!', error);
});
The 400 Bad Request error in more detail:
<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>InvalidArgument</Code>
<Message>Only one auth mechanism allowed; only the X-Amz-Algorithm query parameter, Signature query string parameter or the Authorization header should be specified</Message>
<ArgumentName>Authorization</ArgumentName>
<ArgumentValue>Bearer someToken</ArgumentValue>
<RequestId>someRequestId</RequestId>
<HostId>someHostId</HostId>
</Error>
What I don't understand is, why is it returning 400? and What's the workaround?
Your client is probably sending an initial request that uses an Authorization header, which is being responded with a 302. The response includes a Location header which has a Signature parameter. The problem is that the headers from the initial request are being copied into the subsequent redirect request, such that it contains both Authorization and Signature. If you remove the Authorization from the subsequent request you should be good.
This happened to me, but in a Java / HttpClient environment. I can provide details of the solution in Java, but unfortunately not for AngularJS.
For the Googlers, if you're sending a signed (signature v4) S3 request via Cloudfront and "Restrict Bucket Access" is set to "Yes" in your Cloudfront Origin settings, Cloudfront will add the Authorization header to your request and you'll get this error. Since you've already signed your request, though, you should be able to turn this setting off and not sacrifice any security.
I know this may be too late to answer, but like #mlohbihler said, the cause of this error for me was the Authorization header being sent by the http interceptor I had setup in Angular.
Essentially, I had not properly filtered out the AWS S3 domain so as to avoid it automatically getting the JWT authorization header.
Also, the 400 "invalid argument" may surface as a result of wrong config/credentials for your S3::Presigner that is presigning the url to begin with. Once you get past the 400, you may encounter a 501 "not implemented" response like I did. Was able to solve it by specifying a Content-Length header (specified here as a required header). Hopefully that helps #arjuncc, it solved my postman issue when testing s3 image uploads with a presigned url.
The message says that ONLY ONE authentication allowed. It could be that You are sending one in URL as auth parameters, another - in headers as Authorization header.
import 'package:dio/adapter.dart';
import 'package:dio/dio.dart';
import 'package:scavenger_inc_flutter/utils/AuthUtils.dart';
import 'package:scavenger_inc_flutter/utils/URLS.dart';
class ApiClient {
static Dio dio;
static Dio getClient() {
if (dio == null) {
dio = new Dio();
dio.httpClientAdapter = new CustomHttpAdapter();
}
return dio;
}
}
class CustomHttpAdapter extends HttpClientAdapter {
DefaultHttpClientAdapter _adapter = DefaultHttpClientAdapter();
#override
void close({bool force = false}) {
_adapter.close(force: force);
}
#override
Future<ResponseBody> fetch(RequestOptions options,
Stream<List<int>> requestStream, Future<dynamic> cancelFuture) async {
String url = options.uri.toString();
if (url.contains(URLS.IP_ADDRESS) && await AuthUtils.isLoggedIn()) {
options.followRedirects = false;
options.headers.addAll({"Authorization": await AuthUtils.getJwtToken()});
}
final response = await _adapter.fetch(options, requestStream, cancelFuture);
if (response.statusCode == 302 || response.statusCode == 307) {
String redirect = (response.headers["location"][0]);
if(!redirect.contains(URLS.IP_ADDRESS)) {
options.path = redirect;
options.headers.clear();
}
return await fetch(options, requestStream, cancelFuture);
}
return response;
}
}
I disallowed following redirects.
Used the response object to check if it was redirected.
If it was 302, or 307, (HTTP Redirect Codes), I resent the request after clearing the Auth Headers.
I used an additioal check to send the headers only if the path contained my specific domain URL (or IP Address in this example).
All of the above, using a CustomHttpAdapter in Dio. Can also be used for images, by changing the ResponseType to bytes.
Let me know if this helps you!
I was using django restframework. I applied Token authentication in REST API. I use to pass token in request header (used ModHeader extension of Browser which automatically put Token in Authorization of request header) of django API till here every thing was fine.
But while making a click on Images/Files (which now shows the s3 URL). The Authorization automatically get passed. Thus the issue.
Link look similar to this.
https://.s3.amazonaws.com/media//small_image.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=XXXXXXXXXXXXXXXXXXXXX%2F20210317%2Fap-south-XXXXXXXXFaws4_request&X-Amz-Date=XXXXXXXXXXXXXXX&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
I lock the ModHeader extension to pass Authorization Token only while making rest to REST API and not while making resquest to S3 resources. i.e. do not pass any other Authorization while making request to S3 resource.
It's a silly mistake. But in case it helps.
Flutter: if you experience this with the http dart package, then upgrade to Flutter v2.10!
Related bugs in dart issue tracker:
https://github.com/dart-lang/sdk/issues/47246
https://github.com/dart-lang/sdk/issues/45410
--> these has been fixed in dart 2.16, which has been shipped with Flutter v2.10!
https://medium.com/dartlang/dart-2-16-improved-tooling-and-platform-handling-dd87abd6bad1

Basic Authentication in CakePHP

I am trying to setup Basic Authentication for my CakePHP app so I can use it as an API for an upcoming mobile application. However If I pass the following:
cameron:password#dev.driz.co.uk/basic/locked/
Where cameron is the username, password is the password, and the rest is the domain and application. locked is a method that requires authentication. (obviously the password is wrong in this example)
(Q1) I will be requested for a username and password in a prompt... but the username and password are in fact correct as if I then type them into the prompt they work... Why would this happen? Haven't I just passed the username and password?
I can't see anything wrong with the way I have set this up in CakePHP.
I set Basic Auth in AppController as:
public $components = array('Auth');
function beforeFilter()
{
parent::beforeFilter();
$this->Auth->authorize = array('Controller');
$this->Auth->authenticate = array('Basic');
$this->Auth->sessionKey = false;
$this->Auth->unauthorizedRedirect = false;
}
(Q2) Even so I have set both sessions to be false and the redirect to false, if the user cancels the prompt then they are redirected to the login page? Any ideas on how to stop this from happening? Ideally I want to send back a JSON response or status code of 401 (depending if it's an AJAX request or not).
So something like:
if ($this->request->is('ajax')) {
$response = json_encode(
array(
'meta'=>array(
'code'=>$this->response->statusCode(401),
'in'=>round(microtime(true) - TIME_START, 4)
),
'response'=>array(
'status'=>'error',
'message'=>'401 Not Authorized'
)
)
);
// Handle JSONP
if(isset($_GET['callback'])) {
$response = $_GET['callback'] . '(' . $response . ')';
}
// Return JSON
$this->autoRender = false;
$this->response->type('json');
$this->response->body($response);
} else {
header('HTTP/1.0 401 Unauthorized');
}
But where would this go in the application logic to show this? It needs to happen for ALL requested methods that require authentication and user fails or cancels the authentication.
(Q3) If you enter incorrect details you are just shown the prompt again until you get the username/password correct or hit cancel. How can I make it show an error?
Any ideas for these three issues (marked as sub questions numbers).
Update: This is how I send the headers to the API:
"use strict";jQuery.base64=(function($){var _PADCHAR="=",_ALPHA="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",_VERSION="1.0";function _getbyte64(s,i){var idx=_ALPHA.indexOf(s.charAt(i));if(idx===-1){throw"Cannot decode base64"}return idx}function _decode(s){var pads=0,i,b10,imax=s.length,x=[];s=String(s);if(imax===0){return s}if(imax%4!==0){throw"Cannot decode base64"}if(s.charAt(imax-1)===_PADCHAR){pads=1;if(s.charAt(imax-2)===_PADCHAR){pads=2}imax-=4}for(i=0;i<imax;i+=4){b10=(_getbyte64(s,i)<<18)|(_getbyte64(s,i+1)<<12)|(_getbyte64(s,i+2)<<6)|_getbyte64(s,i+3);x.push(String.fromCharCode(b10>>16,(b10>>8)&255,b10&255))}switch(pads){case 1:b10=(_getbyte64(s,i)<<18)|(_getbyte64(s,i+1)<<12)|(_getbyte64(s,i+2)<<6);x.push(String.fromCharCode(b10>>16,(b10>>8)&255));break;case 2:b10=(_getbyte64(s,i)<<18)|(_getbyte64(s,i+1)<<12);x.push(String.fromCharCode(b10>>16));break}return x.join("")}function _getbyte(s,i){var x=s.charCodeAt(i);if(x>255){throw"INVALID_CHARACTER_ERR: DOM Exception 5"}return x}function _encode(s){if(arguments.length!==1){throw"SyntaxError: exactly one argument required"}s=String(s);var i,b10,x=[],imax=s.length-s.length%3;if(s.length===0){return s}for(i=0;i<imax;i+=3){b10=(_getbyte(s,i)<<16)|(_getbyte(s,i+1)<<8)|_getbyte(s,i+2);x.push(_ALPHA.charAt(b10>>18));x.push(_ALPHA.charAt((b10>>12)&63));x.push(_ALPHA.charAt((b10>>6)&63));x.push(_ALPHA.charAt(b10&63))}switch(s.length-imax){case 1:b10=_getbyte(s,i)<<16;x.push(_ALPHA.charAt(b10>>18)+_ALPHA.charAt((b10>>12)&63)+_PADCHAR+_PADCHAR);break;case 2:b10=(_getbyte(s,i)<<16)|(_getbyte(s,i+1)<<8);x.push(_ALPHA.charAt(b10>>18)+_ALPHA.charAt((b10>>12)&63)+_ALPHA.charAt((b10>>6)&63)+_PADCHAR);break}return x.join("")}return{decode:_decode,encode:_encode,VERSION:_VERSION}}(jQuery));
$(document).ready(function(){
var username = 'cameron';
var password = 'password';
$.ajax({
type: 'GET',
url: 'http://dev.driz.co.uk/basic/locked',
beforeSend : function(xhr) {
var base64 = $.base64.encode(username + ':' + password);
xhr.setRequestHeader("Authorization", "Basic " + base64);
},
dataType: 'jsonp',
success: function(data) {
console.log(data);
},
error: function(a,b,c) {
//console.log(a,b,c);
}
});
});
Q1
You don't specify how you visit the protected URL (dev.driz.co.uk/basic/locked). Are you sure that the way you are doing it you are setting up the request headers properly? You need to Base64 encode the username/password.
When your first request fails the browser jumps in with the prompt and to be succeeding means that the browser does it properly for you the second time.
Have a look at you request headers to see what you send the first time and what the browser sends the second.
Q2
When basic auth fails your server sends a 401 with a header WWW-Authenticate:Basic which is picked up from the browser and you are presented with the prompt. That is build in normal behavior for all browsers since ages, you can't change that.
About your issue with canceling and being redirected to login, Auth had some API changes after 2.4 that are highlighted in the book. Before version 2.4 you are always redirected to loginAction.
Finally, let Auth do the work for you by setting it up properly and don't attempt to hardwire the responses yourself like in the code you suggest. You also shouldn't ever be using php's header() in cakephp, use CakeRequest::header() instead.
Q3
Answered in Q2, you can't have Basic and 401 not trigger the prompt. Either change the required authentication header (by perhaps setting a name like Basic-x instead of Basic) or don't send the response code 401 on failure but send i.e. 200 or 400 and add an error message explaining the situation.

Resources