CakePHP - reject requests if application cookie is not set - cakephp

complete PHP newcomer here. I have a 3rd party PHP application (developed by someone else but I have code to it) that sets a cookie when a user login into its login page. Lets call it MYAPPCOOKIE. I also have a CakePHP based API that enables developers to extract information via an easy to use API.
What I want to do is disable access to the APIs if MYAPPCOOKIE is not set. In other words, rely on the user logged into the portal before I return API values. I know this is a weak authentication mechanism, but I'd really like to start here.
I understand my app and my cake APIs are two different entities. So to get started, I first edited api/app/Config/core.php and added the following line:
Configure::write('Session', array(
'defaults' => 'php',
'cookie' => 'MYAPPCOOKIE'
));
I assume this will give my Cake layer access to the same cookie that the main app uses.
Now, how do I go about denying API access if this cookie does not exist? Do I have to go to every controller of every API and do something like
$this->Cookie->read('MYAPPCOOKIE');
and then do the rest of the work? Or is there a more general way for me to check in the Cake Layer if the instance that is accessing the API also has that cookie set and if not, just return a 4xx for all APIs?

So I finally solved this issue the following way:
a) As it turned out using cookies is a bad idea. There is no way to actually delete a cookie - you can only expire it - the browser decides when to delete it. So even if you change the cookie to have an expired time on logout in the app, the cookie still physically exists and the next time you check isset($_COOKIE[cookiename]) in the cakePHP app, it still exists, which means you can't really use this method to make sure you are logged in to the App (PHP) before you access the API (CakePHP)
b) In my case, the solution was to instead use $_SESSION - the PHP app sets a username and other details when a person is logged in and clears the session when you log out, so to make sure the API ties into the login system of the App, I added the following code to AppController.php:
public function beforeFilter() {
if (!$this->Session->Read('username'))
{
throw new NotFoundException(__('Not Authenticated'));
return;
}
}

Related

Django and react login with google authentication

I was trying set up google authentication with react frontend and django rest framework backend. I set up both the frontend and backend using this two part tutorial, PART1 & PART2. When I try to login with google in the frontend I get POST http://127.0.0.1:8000/google-login/ 400 (Bad Request) I think it's because my google api needs an access token and an authorization code to be passed. After debugging the react js, I noticed the response I get from google doesn't have an authorization code. I suspect because responseType is permission(by default), Source:React login props , instead of code. I was wondering how would you change the response type in react? (I'm not even sure if this alone is the issue)
Here's my backend code
In my views.py file
class GoogleLogin(SocialLoginView):
adapter_class = GoogleOAuth2Adapter
callback_url = "http://localhost:3000"
client_class = OAuth2Client
in my urls.py
path('google-login/', GoogleLogin.as_view(), name='google-login'),
for my front end
/Components/login.js
const googleLogin = async (accesstoken,code) => {
console.log(accesstoken)
let res = await cacaDB.post(
`google-login/`,
{
access_token: accesstoken,
code: code
}
);
console.log(res);
return await res.status;
};
const responseGoogle = (response) => {
console.log(response.code);
googleLogin(response.accessToken, response.code);
}
return(
<div className="App">
<h1>LOGIN WITH GOOGLE</h1>
<GoogleLogin
clientId="client_id"
buttonText="LOGIN WITH GOOGLE"
onSuccess={responseGoogle}
onFailure={responseGoogle}
/>
</div>
)
I want to save the user in the database and have them stay logged in, in the front end.
This Post explains the login flow behind the scene. Here's Login flow image I'm basically stuck on returning code and accesstoken(I can return this successfully) step.
Here's my list of questions,
How do I return code from google?
I have knox token set up, can I
use it instead of the JWT tokens?
Does the class GoogleLogin(SocialLoginView), take care of the steps of validating the access token and code with google and creating the user with that email in database?
Would really appreciate your inputs.
After investigating a bit on my end, I think I might have a solution that works for you.
I've messed with OAuth before, and it's quite tricky sometimes because it has to be robust. So a bunch of security policies usually get in the way.
I'll provide my full step-by-step, since I was able to get it working, trying my best to match what you posted.
Firstly, to have a clean slate, I went off the example code linked in the tutorials. I cloned and built the project, and did the following:
Creating a new project on GCP
Configured the OAuth consent screen
I set the User type to "internal". This options may not be available if you're not using an account under GSuite (which I am). "External" should be fine though, just that "internal" is the easiest to test.
Created a OAuth 2.0 Client
Added http://localhost:3000 to the "Authorized JavaScript origins" and "Authorized redirect URIs" sections
Register a Django superuser
Registered a Site, with value of localhost:8000 for both fields.
Went into the admin panel, and added a Social Application with Client ID and Secret Key as the "Client ID" and "Client Secret" from GCP, respectively. I also picked the localhost site that we added earlier and added it to the right hand box. (I left Key blank)
Example of my Application Page
Filled in the clientId field in App.js, in the params of the GoogleLogin component.
Here's where I ran into a bit of trouble, but this is good news as I was able to reproduce your error! Looking at the request in the network inspector, I see that for me, no body was passed, which is clearly the direct cause of the error. But looking at App#responseGoogle(response), it clearly should pass a token of some sort, because we see the line googleLogin(response.accessToken).
So what is happening is that accounts.google.com is NOT returning a proper response, so something is happening on their end, and we get an invalid response, but we fail silently because javascript is javascript.
After examining the response that Google gave back, I found this related SO post that allowed me to fix the issue, and interestingly, the solution to it was quite simple: Clear your cache. I'll be honest, I'm not exactly sure why this works, but I suspect it has something to do with the fact that development is on your local machine (localhost/127.0.0.1 difference, perhaps?).
You can also try to access your site via incognito mode, or another browser, which also worked for me.
I have knox token set up, can I use it instead of the JWT tokens?
I don't think I have enough knowledge to properly answer this, but my preliminary research suggests no. AFAIK, you should just store the token that Google gives you, as the token itself is what you'll use to authenticate. It seems that Knox replaces Django's TokenAuthentication, which means that Knox is in charge of generating the token. If you're offloading the login work to Google, I don't see how you could leverage something like Knox. However, I could be very wrong.
Does the class GoogleLogin(SocialLoginView), take care of the steps of validating the access token and code with google and creating the user with that email in database?
I believe so. After successfully authenticating with Google (and it calls the backend endpoint correctly), it seems to create a "Social Account" model. An example of what it created for me is below. It retrieved all this information (like my name) from Google.
Example of my "Social Accounts" page
As for how to retrieve the login from the browser's local storage, I have no idea. I see no evidence of a cookie, so it must be storing it somewhere else, or you might have to set that up yourself (with React Providers, Services, or even Redux.

Azure AD - adaljs...how to use this library?

I'm using adaljs. Everything seems great...I can log in, log out, wonderful.
Now, after an hour...I load up my app again and! NOT so wonderful. I debug and I see that adalAuthenticationService.userInfo.isAuthenticated == false and adalAuthenticationService.profile == undefined.
What do I do when I get this? How do I recover?
When do I use these functions and for what?
acquireToken
clearCache
clearCacheForResource
getCachedToken
getResourceForEndpoint
getUser
logOut
logIn
logOutInProgress
Most importantly, WHY are these not explained in detail (or even in brief!) on the adaljs repository?
Let's turn this into a wiki about adaljs functions and properties. We all want to know what they do, what they are for, and how to use them.
Edit
In my app.js, I have this code for handling authentication:
if (adalAuthenticationService.userInfo.isAuthenticated && adalAuthenticationService.userInfo.profile) {
var great = "everything is awesome";
_ld.extend($scope.user,adalAuthenticationService.userInfo);
$scope.successFullyLoggedIn($scope.user);
} else if(!adalAuthenticationService.userInfo.isAuthenticated && !adalAuthenticationService.userInfo.profile) {
adalAuthenticationService.clearCache();
adalAuthenticationService.login();
} else {
adalAuthenticationService.clearCache();
adalAuthenticationService.logOut();
}
The tokens that adal.js gets from AAD expires after every one hour. If you are using angular wrapper then adal will be able to automatically renew the tokens as long as there is a valid user logged in. If you are not using angular wrapper, then application has to take the responsibility of renewing the tokens, that is where the apis will come handy.
I will try to explain what each one of them do:
acquireToken: This is one of the main methods. Takes 2 parameters: resourceId and callback. ResourceId is the app id of application on azure portal. Adal first looks into cache to check if there is token present already. If not, it sends a request to AAD to get a new token. It passes the token or the error to the callback, which has a signature like this: callback(err, token).
clearCache: delete all items in the browser storage that adal stored. Kind of a fresh start.
clearCacheForResource: I am assuming you are using experimental version because this api is not part of released 1.0.12 version. This basically deletes the cache entries for a specific resource id.
getCachedToken: Looks into the caceh and returns the token or null for the given resource.
getResourceForEndpoint: This is useful when using angular wrapper. This looks into the endpoints mapping that user provided at the time of initializing adal. The purpose of this method is to resolve a url into resourceId which can then be fed to acquireToken method.
getUser: current logged in user object or null.
logOut: logs out the user, clears cache and redirect the user to postlogoutredirecturi (if specified), otherwise redirects the user to redirecturi.
login: logs in the user, creates the user object (which can be accessed using getUser), saves the id token in the cache. It also calls the callback that you have on application level on config object when initializing adal.
logOutInProgress: Do you mean loginInProgress? That is just a flag to use internally to see if there is an active login in progress.
Unfortunately, we do not have api guides, that can help people understand our apis better. I will bring this to team's notice. Thanks.
Back to your question: you can use getUser() to get the user object. If it is null, then call logIn() method to log in the user. If user.userName is not null but user.isAuthenticated is false, this means, the token has expired, call acquireToken method and pass clientId of the application to renew the token. Also, you want to check adalAuthenticationService.usernInfo.profile instead of adalAuthenticationService.profile.

Using Multiple Angular App and Session Management

I have 4 angular applications one is a landing app which asks user to login and has to redirect the user according to its type
to one of the other 3 applications. I am unable to figure how to should i achieve that.
Have the three apps running on different subdomains. Upon login backend send a redirect response, figuring out what type of user it is?
But this leads to cors Error. Also i am not sure whether the cookie which i am setting will be accessible in all the subdomains or not.
Is there a way out?
You can do a redirect, but it seems like an unnecessary step (and kind of convoluted for this type of application).
Instead of returning a redirect based on login, it seems more straightforward to just return the address you want to redirect to in the response. Trigger a lookup to determine which app you should be directing to (however you're doing that) and then return the address of the app in the response data. From within Angular, you can extract the address from within response.data in $http. (see angular docs). The nice thing here is you also keep routing control and knowledge of state within Angular itself.
As for the apps themselves--instead of a subdomain, you can simply put the apps into different folders on your domain. This deals with CORS and the cookie issue.
Otherwise, you'd need to set a CORS header. You would do this on whatever backend you're sending the requests to--there's usually some sort of library to make it easy, for example, Flask CORS for Flask. If you need to share cookies in this case, this StackOverflow answer discusses one way of doing it (using an intermediary domain).
Generate a security key for the user session with some TTL in an authentication table when you authenticate the user with your App1
Redirect the user to any other app in any domain with this security key where they can query the authentication table and verify the user.
Let these other applications work on their own (in the front end) and communicate with the back-end with the security key when necessary.
Lot of PHP frameworks has built-in support for this mechanism. My favorite is Silex.

Restricting API Calls to a Certain Domain

My app uses JS Facebook API to use Facebook as a login/pass. Here what happens when you try to login.
User click on the Facebook Login Button
Facebook Authenticates
If Success. I grab the Facebook ID and Name of the user
Calls on my REST API on my APP to check and see if the that FBID is registered in my system.
If Registered, I write the session to verify that the user is authenticated.
This is great since I don't have to store usernames and password. But I am worried that someone will just use a REST API debugger like POSTMAN in chrome and just send a Facebook ID and the name of the user and they will be authenticated.
My question is what is the best way to secure my end that will prevent apps like POSTMAN to just input the fields needed to authenticate? Am I missing something? Can anyone recommend a strategy for this?
Or is using CSRF token the only way to combat this? I am using FuelPHP as a backend and doing a single page app using AngularJS with NgRoutes. But every time I enabled the CSRF on fuel, the token passed does not match what it was in the back-end.
I am under the impression that this is due to that the javascript token function is in the main page, where the ng-view. I know this might have something to do with the ngRoutes.
http://fuelphp.com/docs/classes/security.html
Use Fuel's Auth package. It has Opauth integration which does all the above, and for an entire list of social media platforms, not only facebook.
Always try not to reinvent the wheel, assume someone else has had the same challenge, solved at, and shared the solution with the community.

CakePHP 2.0 Problems with loginRedirect - https removed

I have the following problem.
A reverse proxy is used to establish a secure (https) connection to a server in an internal network. So the address is https://<url>. If I am now on my login page (https://<url>/users/login) and hit "submit" the https is removed (by the loginRedirect) and it's trying to connect to the url via http. Of course that is running into an timeout but the users is logged in. If the user is then accessing a page directly it's working.
Once the users is on the page he can do everything that is necessary and the other links are showing up with https as expected.
How can I avoid that the loginRedirect is removing the https? One solution is to use simply $this->redirect('<url>'); instead of $this->redirect($this->Auth->redirect()); but then I would lose the handling of direct links if somebody is not logged in and tries to access a specific part of the page that requires authentication.
I just got help in the CakePHP lighthouse forum and I would like to share the solution with you. It was actually pretty easy. In a reverse proxy scenario you have to edit the bootstrap.php (in the config folder) and add the following base URL:
define('FULL_BASE_URL','https://your base url');

Resources