Symfony 4 - User becomes null on callback from external API service - symfony

On Symfony 4, when catching a callback route from any external API service (in this case - Shopify API), my logged in user becomes anon.
(HTTP): Everything works when testing on localhost
(HTTPS): However, my logged in User becomes null / Anonymous when testing on my remote server (prod).
How do I fetch my logged in user after catching a callback route from any API service? I think it could be a problem with either HTTP vs HTTPS or some Symfony settings.
On Shopify API dashboard - Allowed redirection URL(s):
http://localhost:8000/shopify/callback
https://<myremoteip>.com/shopify/callback
Symfony Controller Route (for Shopify callback):
/**
* #Route("/shopify/callback", name="shopify_callback")
*/
public function shopify_auth_callback(Request $request)
{
dd($this->getUser());
}
Callback Result (localhost):
App\Entity\User {#977 ▼
-id: 103
-email: "aaaa#gmail.com"
}
Callback Result (remote):
null

I had the same issue but with the Google Oauth system.
I just changed the cookie samesite policy in framework configuration from 'strict' to 'lax' and it solved my issue
Now I can keep the user logged in after api redirection
framework:
session:
enabled: true
cookie_secure: 'auto'
cookie_samesite: 'lax'
cookie_lifetime: 86400

The problem was that I was creating a new session before navigating to a remote URL.
Advice for future readers - make sure you're always on the same session, which you can fetch from the Request.
Avoid doing this:
$session = new Session();

Related

How to achieve an authentication level that allows me to pass "Full authentication is required to access this resource"

I am trying to debug a Resque setup in an (inherited) app, and so I found that there is a route for resque at /hidden/resque that would be nifty to access, but I am unable to access the route. I am wondering what I need to do ... When I try to access that route I get a HTTP 500 due to this error being thrown:
Symfony\Component\Security\Core\Exception\InsufficientAuthenticationException: Full authentication is required to access this resource.
I have tried accessing it both as a web page (after authenticating as an admin role on a different route) and using curl -H 'Authorization: Basic 9339034147964aebec6716c0110311d1' 'https://web.mysite/hidden/resque' -v. No go.
So what constitues "full authentication"? I am already logged in as an admin user on one of the other routes. Would I need to add anything more to the below config? This has not been setup by me, so I would not know if it ever worked.
app/config/routing.yml
ResqueBundle:
resource: "#ResqueBundle/Resources/config/routing.xml"
prefix: /hidden/resque
app/config/security.yml
access_control:
- { path: ^/hidden, roles: ROLE_ADMIN }
According to the docs:
IS_AUTHENTICATED_FULLY: This is similar to IS_AUTHENTICATED_REMEMBERED, but stronger. Users who are logged in only because of a "remember me cookie" will have IS_AUTHENTICATED_REMEMBERED but will not have IS_AUTHENTICATED_FULLY.
How can I be "more logged in" than using a cookie? Should I send a basic auth header with username and password base64 encoded?
If you ask for full authentication.
I.E:
/**
* Requiring IS_AUTHENTICATED_FULLY
*
* #IsGranted("IS_AUTHENTICATED_FULLY", message="Nope, no access")
*/
Then when you are logging in with an user, your Authorization Checker must have granted you the IS_AUTHENTICATED_FULLY status in order to have access.
As explained in the docs:
IS_AUTHENTICATED_FULLY: This is similar to IS_AUTHENTICATED_REMEMBERED, but stronger. Users who are logged in only because of a "remember me cookie" will have IS_AUTHENTICATED_REMEMBERED but will not have IS_AUTHENTICATED_FULLY.
You will be completely Authenticated if you manually log in, and not via a cookie. If you are using a command that remembers your credentials, that might be the issue.
Check Doc nº3 to see whether your actual way of entering that route falls inside the IS_REMEMBERED status. Even maybe you end up prefering using the less restrictive IS_AUTHENTICATED_REMEMBERED
Check the different documentations here:
https://symfony.com/doc/3.4/security.html#checking-to-see-if-a-user-is-logged-in-is-authenticated-fully
https://symfony.com/doc/3.4/security.html#learn-more
https://symfony.com/doc/3.4/security/remember_me.html
https://symfony.com/doc/3.4/components/security/authorization.html#authorization-checker
https://github.com/symfony/symfony/blob/3.4/src/Symfony/Component/Security/Core/Authorization/AuthorizationChecker.php

Session cookie not being set on server

I changed OAuth2 Login form frontend to backend implementation. OAuth2Login is setup using spring security configuration.
Everything worked in local machine.
But on server it doesn't create Session Cookie anymore. Which, I guess, creates the following exception:
org.springframework.security.oauth2.core.OAuth2AuthenticationException: [authorization_request_not_found]
at org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter.attemptAuthentication(OAuth2LoginAuthenticationFilter.java:165) ~[spring-security-oauth2-client-5.1.2.RELEASE.jar!/:5.1.2.RELEASE]
So it wasn't entirely accurate that the session cookie wasn't being set. I think I looked it wrongly in chrome when I was looking it in production. But the cookie was seemed to be reset.
spring:
security:
oauth2:
client:
registration:
google:
redirectUri: https://<youurl>/login/oauth2/code/google
When I added this it created new problem:
org.springframework.security.oauth2.core.OAuth2AuthenticationException: [invalid_redirect_uri_parameter]
at org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeAuthenticationProvider.authenticate(OidcAuthorizationCodeAuthenticationProvider.java:132) ~[spring-security-oauth2-client-5.1.2.RELEASE.jar!/:5
.1.2.RELEASE]
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:175) ~[spring-security-core-5.1.6.RELEASE.jar!/:5.1.6.RELEASE]
at org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter.attemptAuthentication(OAuth2LoginAuthenticationFilter.java:186)
This is because redirectUri is HTTPS but google always returns HTTP
So I ended up adding reading a Stack Overflow post and added a OncePerRequestFilter to my project. Since it's google that is sending this over HTTP they must be thinking it's secure. Couldn't find any way to make google send it over HTTPS

Symfony login form doesn't work

In my Symfony project I have a members zone accessible only by logged users then I write this in my security.yml (access_control):
{ path: ^/membre/, role: IS_AUTHENTICATED_REMEMBERED }
I did too a form with fos_user_security_check action and a _target_path = detail_page_membre.
When I try to log my user, I was redirected to / and in my log I have this:
User has been authenticated successfully. {"username":"toto"} []
Matched route "detail_page_membre".
Populated the TokenStorage with an anonymous Token.
Access denied, the user is not fully authenticated; redirecting to
authentication entry point.
I don't have write any firewalls maybe it's this?
This issue often happens because of the session:
check your session configuration under framework in config.php file:
framework:
session:
# handler_id set to null will use default session handler from php.ini
handler_id: ~
Read more:
Logging in redirects to the login page with no errors
Symfony2 - Access is denied (user is not fully authenticated)

Symfony2: Unable to login successfully with two firewalls using two user providers

I am setting up a website which I want to use separate firewalls and authentication systems for frontend and backend. So my security.yml is configured as below. I am using in_memory user provider in early development phase.
security:
encoders:
Symfony\Component\Security\Core\User\User: plaintext
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
providers:
backend_in_memory:
memory:
users:
admin: { password: admin, roles: [ 'ROLE_ADMIN' ] }
frontend_in_memory:
memory:
users:
user: { password: 12345, roles: [ 'ROLE_USER' ] }
firewalls:
# (Configuration for backend omitted)
frontend_login_page:
pattern: ^/login$
security: false
frontend:
pattern: ^/
provider: frontend_in_memory
anonymous: ~
form_login:
check_path: login_check_route # http://example.com/login_check
login_path: login_route # http://example.com/login
access_control:
# (Configuration for backend omitted)
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, roles: ROLE_USER }
I have omitted the backend part because it doesn't matter. The problem is still there when the omitted part is commented out.
The problem is that frontend authentication won't work with the above configuration. Here's what I did:
Visit http://example.com/login
Enter the credential (user:12345), click login
http://example.com/login_check authenticates the user
The authentication service redirects user back to http://example.com/. No error is thrown. In fact, when I turned on the debug_redirects option, it clearly shows that "user" is authenticated on the redirect page.
Expected behavior: The security token should show that I'm logged in as "user" after following the redirect and go back to the index page.
Actual behavior: The security token still shows "anonymous" login after following the redirect and go back to the index page.
But with nearly identical settings (paths and route names aren't the same), the backend part works correctly.
After some investigation I found that the cause is the way user providers is currently written. Notice that frontend_in_memory section is placed below backend_in_memory that is used for backend authentication. So I explicitly specify the frontend_in_memory provider for the frontend firewall. And it kind of works - I must login with "user:12345" in the frontend login page. Logging in with "admin" won't work. So it must be using the correct user provider. But I suspect that the framework cannot update the security token correctly because it is still searching the "user" account from the first user provider which is backend_in_memory. In fact I can make the above config work with either one of the following changes:
add "user" login to the backend_in_memory provider's user list (password needn't be the same), or
swap frontend_in_memory with backend_in_memory so that frontend_in_memory becomes the first user provider.
Of course they are not the correct way of solving this problem. Adding "user" account to the backend makes no sense at all; swapping the order of two user providers fixes the frontend but breaks the backend.
I would like to know what's wrong and how to fix this. Thank you!
I was stuck when I posted the question, but after a sleep the answer is found ;)
Turns out I came across an issue reported long ago:
https://github.com/symfony/symfony/issues/4498
In short,
The problem isn't about the configuration.
And it isn't about authentication neither.
It actually relates to how an authenticated user is refreshed after redirection. That's why the app is correctly authenticated as "user" on the redirect page, but not after that.
Here is the code when the framework refreshes the user (can be found in \Symfony\Component\Security\Http\Firewall\ContextListener):
foreach ($this->userProviders as $provider) {
try {
$refreshedUser = $provider->refreshUser($user);
$token->setUser($refreshedUser);
if (null !== $this->logger) {
$this->logger->debug(sprintf('Username "%s" was reloaded from user provider.', $refreshedUser->getUsername()));
}
return $token;
} catch (UnsupportedUserException $unsupported) {
// let's try the next user provider // *1
} catch (UsernameNotFoundException $notFound) {
if (null !== $this->logger) {
$this->logger->warning(sprintf('Username "%s" could not be found.', $notFound->getUsername()));
}
return; // *2
}
}
The above code shows how the framework loops through the user providers to find the particular user (refreshUser()). *1 and *2 are added by me. If a user provider throws an UnsupportedUserException, this means that the provider isn't responsible for the supplied UserInterface. The listener will then iterate to the next user provider (*1).
However, if what the user provider thrown is a UsernameNotFoundException, this means that the provider is responsible for the supplied UserInterface, but the corresponding account could not be found. The loop will then stop immediately. (*2)
In my question, the same user provider, \Symfony\Component\Security\Core\User\InMemoryUserProvider, is used in both frontend and backend environment. And InMemoryUserProvider is responsible for the UserInterface implemented by Symfony\Component\Security\Core\User\User.
In the frontend, "user" is in fact authenticated successfully. However, in the user refresh attempt,
The order of the user providers will be like this: backend in-memory provider, frontend in-memory provider.
So, backend in-memory provider will run first.
The backend in-memory provider believes it is responsible for the supplied UserInterface because it is also an instance of Symfony\Component\Security\Core\User\User.
But it fails to locate the "user" account (it only has the "admin" account).
It then throws a UsernameNotFoundException.
The refreshUser() routine won't bother to try with next provider because UsernameNotFoundException means that the responsible user provider is already found. Instead it stops trying and removes the authentication token.
This explains why the configuration won't work. Despite using a different user provider, the only way to work around this is to copy the framework's InMemoryUserProvider and User classes and change the refreshUser() method to check against the copied User class, so that the frontend and backend user provider uses different user classes and won't clash.

No Scopes Specified - Google_Auth_Exception

I am applying this tutorial into symfony 2.4, I've finished the setup in the config.yml and everything, I managed to visit the admin/google/analytics page, but the problem is when I tried to authenticate with the parameters I've created in the config.yml file, it is searching for the scope, here is the parameters.
happy_r_google_analytics:
host: www.example.com
profile_id: MyProfileId
tracker_id: UA-TRACKER-ID
token_file_path: %kernel.root_dir%/var/storage
happy_r_google_api:
application_name: Project Default Service Account
oauth2_client_id: OAuthClientID
oauth2_client_secret: OAuthClientSecret
oauth2_redirect_uri: http://www.example.com/app_local.php/admin/google/analytics/oauth2callback
developer_key: DevelopperKey
site_name: http://www.example.com
I think there's no problem here, I've got no idea where I can set the scope so the google Api client can set it to https://www.googleapis.com/auth/analytics.readonly
You need to define a scope. If you use Google Auth, check Authorization scopes for it.
You must do something like:
$googleClient = new \Google_Client();
$googleClient->setScopes(array(
'https://www.googleapis.com/auth/plus.me',
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/userinfo.profile',
));

Resources