I'm building a new project and I'm having some debate over how it needs to be developed. The big picture is to develop a consumable JavaScript widget that other internal developers can embed into their web applications. The trick is that the consumer needs to be able to tell me what AD user is currently logged into their page...and then I need to trust that the passed username is coming from the consumer and hasn't been tampered with via outside sources.
The overall solution needs to have a VERY simple set-up on the consuming side involving no compiled code changes. Also it needs to be functional across both ASP.net and PHP applications (hence my decision to go with JavaScript).
Overall, it's kind of like an Oauth solution...except the trust between domains can be intrinsic since I'll already know every user in the company trusts the host domain.
I started stubbing it out and got kind of stuck. My idea was that I would basically host a JavaScript file that the client host could embed in their page. During their page load cycle, they could init my JavaScript widget and pass it a plain text username (all I really need). Somehow I would establish an secure trust between the client host's web page, and my widget so that it would be impossible for a third-party to embed my widget into a false web page and send action commands under a user other than their own.
I hope this makes sense to someone.
I haven't really discovered an answer so to speak, but I've decided on a method:
So, I decided on a pattern where I write my JavaScript and HTML widget using the proposed jQuery UI Widget Factory. That allows the my consumer to implement the widget using simple syntax like:
<script src="widget.js"></script>
$('#someElement').myWidget({ encryptionUrl: handlerPath });
Now, you'll noticed that as part of my widget, I ask the consumer to pass a "handlerPath." The "handler" is simply an Microsoft MVC Controller which is in charge of getting the logged in user, and encrypting the call.
So the handler in my app looks something like this...
[Authorize]
public JsonpResult GetToken(string body, string title, string sender)
{
Packet token = new Packet();
try
{
// Get the widget host's public cert
string publicKey = "some.ssl.key.name.here";
// Get the consumer host's private cert
string privateKey = "this.consumers.ssl.key.name.here";
// Build a simple message object containing secure details
// Specifically, the Body will have action items (in JSON) from my widget
// The User will be generated from the consumer's backend, thus secure
Message message = new Message(){
Body = body,
Title = title,
User = System.Web.HttpContext.Current.User.Identity.Name,
EncryptionServerIP = Request.UserHostAddress,
Sender = new Uri(sender),
EncryptionTime = DateTime.Now
};
PacketEncryption encryption = new PacketEncryption();
// This class just wraps basic encryption and signing methods
token = encryption.EncryptAndSign(message, publicKey, privateKey);
token.Trust = "thisConsumerTrustName";
}
catch (Exception exception)
{
throw;
}
return this.Jsonp(token);
}
Now, I have an encrypted "token" which has been encrypted using the widget host's public key, and signed using the widget consumer's private key. This "token" is passed back to the widget via JSONP from the consuming server.
My widget then sends this "token" (still as JSONP) to it's host server. The widget hosting server has decrypting logic which looks something like this.
public Message DecryptAndVerify(Packet packet, string requestIP)
{
if (packet == null) throw new ArgumentNullException("packet");
if (requestIP == null) throw new ArgumentNullException("requestIP");
Message message = new Message();
try
{
// Decrypt using the widget host's private key
RSAEncryption decrypto = new RSAEncryption("MyPrivateKey");
// Verify the signature using the "trust's" public key
// This is important because like you'll notice, I get the trust name
// from the encrypted packet. I then maintain a "trust store" mapping
// in my web.config, or SQL server
RSAEncryption verifyo = new RSAEncryption(GetPublicKeyFromTrust(packet.Trust));
string decryptedJson = decrypto.DecryptString(packet.EncryptedData);
// Verify the signature
if (!verifyo.Verify(decryptedJson, packet.Signature))
{
Exception ex = new Exception("Secure packet was not verified. Tamper evident");
throw ex;
}
// If the message is encrypted correctly, turn it into a message object
message = decryptedJson.FromJson<Message>();
// Verify the ip
if (message.EncryptionServerIP != requestIP)
{
Exception ex = new Exception("Request IP does not match encryption IP. Tamper evident");
throw ex;
}
// Verify the time
if ((DateTime.Now - message.EncryptionTime).Seconds > 30)
{
Exception ex = new Exception("Secure packet is too old");
throw ex;
}
}
catch (Exception ex)
{
throw ex;
}
return message;
}
The idea is that the JavaScript widget determines the secure actions the end user wants to take. Then it calls back to it's host (using the handler path provided by the consumer) and requests an encrypted token. That token contains the IP address of the caller, a timestamp, the current AD username, and a bundle of actions to be completed. Once the widget receives the token, it passes it over to it's own host server at which point the server checks to make sure that it is
Signed and encrypted properly according to predefined trusts
Not older than 30 seconds
From the same IP as the initial request to the consumer's server
After I determine those checks to be valid I can act on the user's actions by creating a WindowsPrincipal identity from the string username like this:
WindowsPrincipal pFoo = new WindowsPrincipal(new WindowsIdentity("username"));
bool test = pFoo.IsInRole("some role");
All said and done, I have established a trusted request from the widget consumer, and I no longer have to prompt for authentication.
Hopefully this helps you out. It's been running in my internal environment for about a month of QA and it's it's working great so far.
Related
I have Facebook and Google login in my application, I use my backend server to store data about the user, such as name and status.
I am sending the token along side with some info like user points, the server uses the token identifies the user and does his work just fine.
Before publishing the app i want to encrypt everything, I know I can use SSL however my provider charges A LOT of money for SSL support.
My idea was to genarate a RSA Keypair, save the private on a safe place, and have the public in the apk.
I can generate encrypt and decrypt using rsa within my app very easily, but I'm not an expert in php i tried a lot of things to decrypt stuff in server side but i can't figure it out how to do it.
I have one Keypair generated by android, i used,
getPublic().getEncoded()
getPrivate().getEncoded()
How can if use the private key in php to decrypt and encrypt data?
I know that this may not be the best way to do things but i think i won't have a problem, the target audience is really far from hackers.
Because you added the tag PHP, i am assuming that you have some kind of rest api running that you are calling from your android app. Now you don't need encrypt and decrypt in PHP. Those are handled by your web servers. As far as ssl goes have a look at let's encrypt which is opensource. Enforcing ssl alone on web server is pretty good security measure.
I think i achived what i was tring to do, login is 100% handle by facebook and google via https, i only use tokens to identity the user in my server and increment the score
1- Token and score is encrypted and sent to the server
2- Using the private key the server finds the token and i use https to make calls to Facebook or Google to retrieve the user id and increment the score
Note that all data stored in my server is 100% public, i don't store private information about anyone, i just want to protect the token, if someone gets the token and starts to make a lot of calls it may reach the facebook limit of 200 calls/hour per user, making my app inoperable.
I will upgrade to SSL in the future, when i start to earn revenue from the app
Android
String pubKeyPEM = "***";
public void something(){
String sendToServer = Base64.encodeToString(RSAEncrypt("test"),0);
}
public byte[] RSAEncrypt(final String request) throws Exception {
PublicKey publicKey = getPublicKey();
cipher = Cipher.getInstance("RSA/None/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(plain.getBytes());
}
public PublicKey getPublicKey() throws Exception {
PublicKey publicKey;
byte[] decoded = Base64.decode(pubKeyPEM, Base64.DEFAULT);
KeyFactory kf = KeyFactory.getInstance("RSA");
publicKey = kf.generatePublic(new X509EncodedKeySpec(decoded));
return publicKey;
}
PHP
$privkey = '-----BEGIN RSA PRIVATE KEY-----';
function decrypt($data){
global $privkey;
if (openssl_private_decrypt(base64_decode($data), $decrypted, $privkey))
$data = $decrypted;
else
$data = '';
return $data;
}
The private key will be moved to a safer place, but this is working just as i wanted
my server is also checking if the token was generated by my app id, so if someone tries to use a diferent token, it will show a diferent app id.
I'm working on trace logger of sorts that pushes log message requests onto a Queue on a Service Bus, to later be picked off by a worker role which would insert them into the table store. While running on my machine, this works just fine (since I'm the only one using it), but once I put it up on a server to test, it produced the following error:
HTTP_Request2_MessageException: Malformed response: in D:\home\site\wwwroot\vendor\pear-pear.php.net\HTTP_Request2\HTTP\Request2\Adapter\Socket.php on line 1013
0 HTTP_Request2_Response->__construct('', true, Object(Net_URL2)) D:\home\site\wwwroot\vendor\pear-pear.php.net\HTTP_Request2\HTTP\Request2\Adapter\Socket.php:1013
1 HTTP_Request2_Adapter_Socket->readResponse() D:\home\site\wwwroot\vendor\pear-pear.php.net\HTTP_Request2\HTTP\Request2\Adapter\Socket.php:139
2 HTTP_Request2_Adapter_Socket->sendRequest(Object(HTTP_Request2)) D:\home\site\wwwroot\vendor\pear-pear.php.net\HTTP_Request2\HTTP\Request2.php:939
3 HTTP_Request2->send() D:\home\site\wwwroot\vendor\microsoft\windowsazure\WindowsAzure\Common\Internal\Http\HttpClient.php:262
4 WindowsAzure\Common\Internal\Http\HttpClient->send(Array, Object(WindowsAzure\Common\Internal\Http\Url)) D:\home\site\wwwroot\vendor\microsoft\windowsazure\WindowsAzure\Common\Internal\RestProxy.php:141
5 WindowsAzure\Common\Internal\RestProxy->sendContext(Object(WindowsAzure\Common\Internal\Http\HttpCallContext)) D:\home\site\wwwroot\vendor\microsoft\windowsazure\WindowsAzure\Common\Internal\ServiceRestProxy.php:86
6 WindowsAzure\Common\Internal\ServiceRestProxy->sendContext(Object(WindowsAzure\Common\Internal\Http\HttpCallContext)) D:\home\site\wwwroot\vendor\microsoft\windowsazure\WindowsAzure\ServiceBus\ServiceBusRestProxy.php:139
7 WindowsAzure\ServiceBus\ServiceBusRestProxy->sendMessage('<queuename>/mes…', Object(WindowsAzure\ServiceBus\Models\BrokeredMessage)) D:\home\site\wwwroot\vendor\microsoft\windowsazure\WindowsAzure\ServiceBus\ServiceBusRestProxy.php:155
⋮
I've seen previous posts that describe similar issues; Namely:
Windows Azure PHP Queue REST Proxy Limit (Stack Overflow)
Operations on HTTPS do not work correctly (GitHub)
That imply that this is a known issue regarding the PHP Azure Storage libraries, where there are a limited amount of HTTPS connections allowed. Before requirements were changed, I was accessing the table store directly, and ran into this same issue, and fixed it in the way the first link describes.
The problem is that the Service Bus endpoint in the connection string, unlike Table Store (etc.) connection string endpoints, MUST be 'HTTPS'. Trying to use it with 'HTTP' will return a 400 - Bad Request error.
I was wondering if anyone had any ideas on a potential workaround. Any advice would be greatly appreciated.
Thanks!
EDIT (After Gary Liu's Comment):
Here's the code I use to add items to the queue:
private function logToAzureSB($source, $msg, $severity, $machine)
{
// Gather all relevant information
$msgInfo = array(
"Severity" => $severity,
"Message" => $msg,
"Machine" => $machine,
"Source" => $source
);
// Encode it to a JSON string, and add it to a Brokered message.
$encoded = json_encode($msgInfo);
$message = new BrokeredMessage($encoded);
$message->setContentType("application/json");
// Attempt to push the message onto the Queue
try
{
$this->sbRestProxy->sendQueueMessage($this->azureQueueName, $message);
}
catch(ServiceException $e)
{
throw new \DatabaseException($e->getMessage, $e->getCode, $e->getPrevious);
}
}
Here, $this->sbRestProxy is a Service Bus REST Proxy, set up when the logging class initializes.
On the recieving end of things, here's the code on the Worker role side of this:
public override void Run()
{
// Initiates the message pump and callback is invoked for each message that is received, calling close on the client will stop the pump.
Client.OnMessage((receivedMessage) =>
{
try
{
// Pull the Message from the recieved object.
Stream stream = receivedMessage.GetBody<Stream>();
StreamReader reader = new StreamReader(stream);
string message = reader.ReadToEnd();
LoggingMessage mMsg = JsonConvert.DeserializeObject<LoggingMessage>(message);
// Create an entry with the information given.
LogEntry entry = new LogEntry(mMsg);
// Set the Logger to the appropriate table store, and insert the entry into the table.
Logger.InsertIntoLog(entry, mMsg.Service);
}
catch
{
// Handle any message processing specific exceptions here
}
});
CompletedEvent.WaitOne();
}
Where Logging Message is a simple object that basically contains the same fields as the Message Logged in PHP (Used for JSON Deserialization), LogEntry is a TableEntity which contains these fields as well, and Logger is an instance of a Table Store Logger, set up during the worker role's OnStart method.
This was a known issue with the Windows Azure PHP, which hasn't been looked at in a long time, nor has it been fixed. In the time between when I posted this and now, We ended up writing a separate API web service for logging, and had our PHP Code send JSON strings to it over cURL, which works well enough as a temporary work around. We're moving off of PHP now, so this wont be an issue for much longer anyways.
I am trying to authenticate with a family history web service that authenticates using OAuth2. The basic workflow of the authentication is that I submit a get request against the web service requesting an authentication session. It returns in the body of the response HTML Code with some login components for user name and password. My PHP application then echoes the html code to the browser. The end user can then enter his or her user name and password, then submit to the web service. This is where the behavior becomes unclear. In theory, The web service should redirect to a predefined redirect URI with some parameters included in the URL. In practice, however, submitting the password redirects to the pre registered redirect URI, but there are no parameters included in the URL. My Project is written primarily in PHP. This is a snippit of the code that makes the inital request for an authentication session.
function logOn($mainURL, $credentials)
{
// create a new HTTP_Request object to be used in the login process
$request = new HTTP_Request();
// set the URL of the HTTP_Request object to the family search identity/login endpoint
$request->setUrl("https://web-service/authentication/path?response_type=code&client_id=".$credentials['key']."&redirect_uri=https://www.myredirectPage.edu/");
$request->_useBrackets = false;
$request->addHeader("User-Agent", $credentials['agent']);
$request->addHeader("Accept","text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
$request->sendRequest();
//HTML_HEADER;
//the response will come in the form of an html file
$responseHtml = $request->getResponseBody();
//Dynamically load html from request body onto the browser to allow for authentication
echo $responseHtml;
return $credentials;
}
The end user will enter their login credentials using the loaded html components and hit submit. The web service then redirects to my redirect authentication page. The code there is provided below.
<?php
// process client request (Via url)
//gather code parameters from the redirect url.
if (isset($_GET['code']))
{
echo $_GET['code'];
}
else
{
echo "code not returned";
}
if (isset($_GET['error']))
{
echo $_GET['error'];
}
else
{
echo "error not returned";
}
?>
Thanks in advance to any help with this.
When I use Google Chrome's Network debugger tool, I saw that my project was making unexpected searches for Javascript and Css resources, all resulting in 404 (not found) errors. Upon closer inspection, I could see that the resources were relative paths to resources that are on the web service server. Rather than looking for 'https://webService.net/js/importantJavascript.js' (an existing file located on the service's web server), it was trying to find 'https://mywebpage.edu/js/importantJavascript.js'(a path to a file that doesn't exist).
I have a browser-based app (single page, AngularJS) and am using hello to use third party signin such as Google, FB, Soundcloud, etc.
My app uses a PHP API server.
What's a good way to have the user able to login using Google, but also verify the user on the server side?
I was considering:
The browser app performs an implicit grant with google/fb/etc
I then transfer the access_token from the client to the server, then use, for example, a google-api-php-client with my app id, secret and the user access_token? Using their API such as /me? (which grant type would this be?)
Retrieve some key from the third-party (facebook_id, email, etc), match it against a user in my database, and then consider the user authenticated?
Also, should I perform this on each API request? Or should I just stash the access_token for a bit and assume that the user is still valid until the key expires?
One issue is that not all of those providers support the implicit flow. But assuming they do, the access_token you get for each will be proof that the user authenticated with that system, not necessarily that they have access to call your API. You still need something that asserts that "someone#gmail.com can 'read' resource X in your system"
You probably need something that translates whatever you get from Google, Soundcloud, etc. into a token your app understands. A simple(r) format is to use JWT. (Json Web Tokens).
App -> Intermmediary -> Soundcloud/Google
<-JWT--+ <---whavetever-+
and then:
App - (JWT) -> API
JWT are easy to manipulate, validate and verify. See jwt.io
You might want to look at this blog post also for some additional information (specifically on AngularJS front-ends)
The blog post #eugenio-pace mentioned was really helpful for setting up the client side.
For the server side though, the access_token should be validated.
The SDK's are (in composer) (code below):
Facebook: "facebook/php-sdk-v4" : "4.0.*"
Google: cURL request (didn't care for "google/apiclient")
SoundCloud: "ise/php-soundcloud": "3.*"
(There are others of course, just these three were the ones I chose, and seem decent.)
Last time I did something like this I made the mistake of validating the access_token on every request, which had a huge (obviously negative) impact on performance. Now I just validate it on login and use it to retrieve the user's ID from that service. So, the browser sends me access_token A and says it's from Facebook, I use the sdk above the the access_token with Facebook, and I get back their ID so I know they are who they say they are.
I'd suggest storing the access_token on the server with the expires_in.
(I haven't dealt with refresh token's yet)
Code to validate tokens using the above libraries:
function validateTokenFacebook($token, $id=null) {
// Performed above
// FacebookSession::setDefaultApplication($config->fb->app_id, $config->fb->secret);
$session = new FacebookSession($token);
// Fetch user info
$request = new FacebookRequest($session, 'GET', '/me');
try {
$response = $request->execute();
} catch (\Facebook\FacebookServerException $e) {
$this->mlog->err($e . "\n" . $e->getTraceAsString());
throw new AuthTokenInvalidException();
}
$graphObject = $response->getGraphObject();
$user_id = $graphObject->getProperty('id');
return array(access_token, $user_id);
}
function validateTokenGoogle($token, $id=null) {
$resp=array();
// This key isn't included in the token from hello.js, but
// google needs it
if (!array_key_exists('created', $token)) $token['created'] = $token['expires'] - $token['expires_in'];
$client = new \Google_Client();
$client->setClientId($this->systemConfig->google->app_id);
$client->setClientSecret($this->systemConfig->google->secret);
$client->setRedirectUri($this->systemConfig->google->redirectUri);
$client->setScopes('email');
$client->setAccessToken(json_encode($token));
try {
// Send Client Request
$objOAuthService = new \Google_Service_Oauth2($client);
$userData = $objOAuthService->userinfo->get();
return array($token['access_token'], $userData['id']);
} catch (\Google_Auth_Exception $e) {
throw new AuthException('Google returned ' . get_class($e));
}
}
function validateTokenSoundcloud($token, $id=null) {
$soundcloud = new \Soundcloud\Service(
$this->systemConfig->soundcloud->app_id,
$this->systemConfig->soundcloud->secret,
$this->systemConfig->soundcloud->redirect);
$soundcloud->setAccessToken($access_token);
try {
$response = json_decode($soundcloud->get('me'), true);
if (array_key_exists('id', $response))
return array($access_token, $response['id']);
} catch (Soundcloud\Exception\InvalidHttpResponseCodeException $e) {
$this->mlog->err($e->getMessage());
}
throw new AuthTokenInvalidException();
}
I have a few custom classes above, such as the Exceptions and the systemConfig, but I think it's verbose enough to communicate what they do.
I'm working with a .net server solution that provides an authentication web service, along with several other service URLs. The access process involves an initial call to the authentication service URL, and using an 'Authenticate' soap call, which is supposed to return an access token. This token is then used to make calls to the other service URLs to retrieve data from the server.
The issue I'm having is that I was provided a username and password for the authentication process, but there's no indication of how the credentials are meant to be sent to the server. Additionally, I'm trying to access the web service (.net based) using PHP.
So far, I've managed to use wsdl2php to generate classes for the authentication service URL, but the classes don't provide any indication of how the username and password are meant to be sent.
I've tried adding the credentials as soap headers:
$headerContent = "<o:UserName xmlns:o=\"$namespace\">
<o:UserName>$uname</o:UserName>
<o:Password>$pword</o:Password>
</o:UserName>";
$headerVar = new SoapVar($headerContent, XSD_ANYXML, null, null, null);
$header = new SoapHeader($namespace, 'o:ClientCredentials', $headerVar);
$this->__setSoapHeaders(array($header));
try {
return $this->__soapCall('Authenticate', array());
} catch (Exception $e) {
throw new Exception( 'Not allowed.', 0, $e);
}
...but I receive an 'Access denied' message. Is there a proper way to pass the credentials to the service in order to successfully authenticate?
There are many different authentication flavors - forms, basic, oauth. If you have a way to login via a browser, I would suggest running Fiddler to peek at the message traffic. Fiddler is a middleware tool that sits between the browser and server. It will give you the packet headers - so you can see the encryption, field formatting and content type. You should then mimic the request.