FOSOUserBundle + HWIOAuthBundle google accesstoken Timeout after a hour - symfony-2.1

Iam using FOSUserBundle + HWIOAuthBundle and have a problem regarding the accesstoken expired after one hour. Here I am saving my access token in db while each login. How to refresh my access token and update in db? is there any document regarding this?

I ran into this problem recently! Was using the stored access token to get contacts.
In the service that gets the contacts, it tries with the currently saved token, and if that doesn't work out, it leverages the user's refresh token to generate a new access token.
My travels led me to a setup like this, but I didn't capture the refresh token on signup. So until I had that figured out, I was euchred.
Additionally, you'll need to ask for 'offline' access in the scope, when defining the google resource in HWIOauth, if you want access to that refresh token.
Contact Retrieving Service:
<?php
namespace Acme\DemoBundle\Services;
use Acme\DemoBundle\Entity\User;
class GoogleContactRetriever
{
private $user;
private $buzz;
public function __construct($buzz, $googleId, $googleSecret, $userProvider)
{
$this->buzz = $buzz;
$this->googleId = $googleId;
$this->googleSecret = $googleSecret;
$this->userProvider = $userProvider;
}
public function setUser($user)
{
$this->user = $user;
}
public function requestContacts()
{
return $this->buzz->get( "https://www.google.com/m8/feeds/contacts/default/full?access_token=".$this->user->getGoogleAccessToken() );
}
public function retrieveUserContacts()
{
$response = $this->requestContacts();
$headers = $response->getHeaders();
if ($headers[0] != 'HTTP/1.0 200 OK') {
$this->refreshAccessToken($this->user);
$response = $this->requestContacts();
}
return $this->parseResponseString($response->getContent());
}
// ...
public function refreshAccessToken($user)
{
$refreshToken = $user->getGoogleRefreshToken();
$response = $this->buzz->post( "https://accounts.google.com/o/oauth2/token", array(),
"refresh_token=$refreshToken&client_id={$this->googleId}&client_secret={$this->googleSecret}&grant_type=refresh_token"
);
$responseContent = json_decode($response->getContent());
$this->userProvider->updateGoogleAccessToken($user, $responseContent->access_token);
}
}
FOSUBProvider
<?php
namespace NYW\Bundle\CoreBundle\Security\Core\User;
use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;
use HWI\Bundle\OAuthBundle\Security\Core\User\FOSUBUserProvider as BaseClass;
use Symfony\Component\Security\Core\User\UserInterface;
class FOSUBUserProvider extends BaseClass
{
/**
* {#inheritdoc}
*/
public function loadUserByOAuthUserResponse(UserResponseInterface $response)
{
$serviceName = $response->getResourceOwner()->getName();
$avatar = null;
$username = $response->getUsername();
$email = $response->getEmail();
$user = $this->userManager->findUserByEmail($email);
//when the user is registrating
if (null === $user) {
$service = $response->getResourceOwner()->getName();
$setter = 'set'.ucfirst($service);
$setter_id = $setter.'Id';
$setter_token = $setter.'AccessToken';
// create new user here
$user = $this->userManager->createUser();
$user->$setter_id($email);
$user->$setter_token($response->getAccessToken());
switch ($service) {
case 'google':
$refreshToken = $response->getRefreshToken();
$user->setGoogleRefreshToken($refreshToken);
break;
}
//I have set all requested data with the user's username
//modify here with relevant data
$user->setUsername($username);
$user->setEmail($email);
$user->setPassword($email);
$user->setEnabled(true);
$this->userManager->updateUser($user);
return $user;
}
//We used to call the parent's loadUserByOAuthUserResponse method here..
$setter = 'set' . ucfirst($serviceName) . 'AccessToken';
//update access token
$user->$setter($response->getAccessToken());
return $user;
}
public function updateGoogleAccessToken($user, $token)
{
$user->setGoogleAccessToken($token);
$this->userManager->updateUser($user);
}
}
config.yml
services:
adb.user_provider:
class: "Acme\DemoBundle\Security\Cure\FOSUBUserProvider"
arguments: [#fos_user.user_manager,{facebook: facebook_id, google: google_id}]
hwi_oauth:
connect:
account_connector: adb.user_provider
resource_owners:
google:
scope: "https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile https://www.google.com/m8/feeds"

Related

IdentityServer3 Refresh Token - Where to invoke the refresh

I am developing a MVC application that uses OpenID and IdentityServer3.
Background:
I am running into a issue that when the Authentication Cookie times out, I need to use the refresh token to generate a new one.
I am able to login and receive the AuthorizationCodeReceived notification, which i use to request an authorization code and retrieve a RefreshToken which I store in the claims of the AuthenticationTicket.
I have tried adding logic to check and refresh the authentication in:
CookieAuthenticationProvider.OnValidateIdentity -- This works to
refresh, and I was able to update the cookie, but it is not called after the cookie expired.
Adding code in the begining of the the ResourceAuthorizationManager.CheckAccessAsync -- this does not work because the identity is null and I cannot retrieve the refresh token claim.
Adding a filter Filter for MVC, but I am unable to figure out what to add as a HttpResponseMessage for WebAPI.
public const string RefreshTokenKey = "refresh_token";
public const string ExpiresAtKey = "expires_at";
private const string AccessTokenKey = "access_token";
private static bool CheckAndRefreshTokenIfRequired(ClaimsIdentity id, out ClaimsIdentity identity)
{
if (id == null)
{
identity = null;
return false;
}
if (id.Claims.All(x => x.Type != ExpiresAtKey) || id.Claims.All(x => x.Type != RefreshTokenKey))
{
identity = id;
return false;
}
//Check if the access token has expired
var expiresAt = DateTime.Parse(id.FindFirstValue(ExpiresAtKey));
if ((expiresAt - DateTime.Now.ToLocalTime()).TotalSeconds < 0)
{
var client = GetClient();
var refreshToken = id.FindFirstValue(RefreshTokenKey);
var tokenResponse = client.RequestRefreshTokenAsync(refreshToken).Result;
if (tokenResponse.IsError)
{
throw new Exception(tokenResponse.Error);
}
var result = from c in id.Claims
where c.Type != AccessTokenKey &&
c.Type != RefreshTokenKey &&
c.Type != ExpiresAtKey
select c;
var claims = result.ToList();
claims.Add(new Claim(AccessTokenKey, tokenResponse.AccessToken));
claims.Add(new Claim(ExpiresAtKey, DateTime.Now.AddSeconds(tokenResponse.ExpiresIn).ToLocalTime().ToString()));
claims.Add(new Claim(RefreshTokenKey, tokenResponse.RefreshToken));
identity = new ClaimsIdentity(claims, id.AuthenticationType);
return true;
}
identity = id;
return false;
}
Links:
How would I use RefreshTokenHandler?
Identity Server3 documentation
Looked at the two examples, but using resourceowner flow for openid doesn't seem the right way. The MVC code flow relies on the User still having the principle, but my claims are all empty in the resource authorize.
EDIT:
Okay, so if I set the AuthenticationTicket.Properties.ExpiresUtc to null in AuthorizationCodeReceived, it is setting it to null then somewhere down the line it is setting it to 30 days instead of 5 minutes (I searched the katana and identity server source code but could not find where it is setting this value), which I can live with, but would prefer it to be the same as the browser where it is "Session"
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
CookieManager = new SystemWebChunkingCookieManager(),
Provider = new CookieAuthenticationProvider()
{
OnValidateIdentity = context =>
{
ClaimsIdentity i;
if (CheckAndRefreshTokenIfRequired(context.Identity, out i))
{
context.ReplaceIdentity(i);
}
return Task.FromResult(0);
}
}
});
The problem was that in the AuthorizationCodeRecieved notification I was passing the Properties from the original ticket, which had the timeout set for Expires for the authorization code Changing the the code to pass null in resolved the issue and allowed the CookieAuthenticationHandler.ApplyResponseGrantAsync to pass its own properties.
var claimsIdentity = new ClaimsIdentity(id.Claims, n.AuthenticationTicket.Identity.AuthenticationType, "name", "role");
n.AuthenticationTicket = new AuthenticationTicket(claimsIdentity, null);

Manual force-authentication of a user without issuing an authentication request

I have a ServiceStack application that coexists with mvc5 in a single web project. The only purpose of the mvc5 part is to host a single controller action that receives a callback from janrain for javascript initiated social login. I could receive this callback in a SS service request, too, but then I don't know how I would do a redirect to the returnUrl that is passed through all the way from the javascript context. Even if I was able to figure this out, my question would still be the same.
Inside of the controller action, once I verify the janrain provided token resolves to a user in my system, I need to manually tell ServiceStack "hey trust me - this person is authorized".
All my searches lead to some code along the lines of the following snippet:
var authService = AppHostBase.Resolve<AuthService>();
authService.RequestContext = System.Web.HttpContext.Current.ToRequestContext();
var AuthResponse = authService.Authenticate(new Auth
{
provider = "credentials",
UserName = user.user_id,
Password = user.password,
RememberMe = true
});
My first problem here is that I store hashed passwords (I support social login as well as manual login), so I don't know the user's password (and I shouldn't).
My second problem is that this code seems to only work for SS 3.X and not 4.X. I requires a ServiceStack.ServiceInterface.dll that is mysteriously missing from 4.X.
Is there a short and precise way to manually authenticate a user with SS on the server side?
Thanks
EDIT:
So far this is what I am doing: (This is not final code - I have commented out some things I don't know what to do with):
public class UsernameOnlyAuthorizationService : Service
{
public object Post(UsernameOnlyLoginRequest request)
{
var authProvider = new UsernameOnlyAuthProvider();
authProvider.Authenticate(this, GetSession(), new Authenticate()
{
UserName = request.username,
Password = "NotRelevant",
RememberMe = true
});
return HttpResult.Redirect(request.returnUrl);
}
}
public class UsernameOnlyAuthProvider : CredentialsAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
var authRepo = authService.TryResolve<IAuthRepository>().AsUserAuthRepository(authService.GetResolver());
ReferScienceDataContext db = authService.TryResolve<ReferScienceDataContext>();
var session = authService.GetSession();
IUserAuth userAuth;
var user = db.Users.FirstOrDefault(u => u.Username == userName);
if (user != null)
{
//AssertNotLocked(userAuth);
//session.PopulateWith(userAuth);
session.Id = user.Id.ToString();
session.UserName = user.Username;
session.FirstName = user.FirstName;
session.LastName = user.LastName;
session.IsAuthenticated = true;
session.UserAuthId = user.Id.ToString(CultureInfo.InvariantCulture);
session.ProviderOAuthAccess = authRepo.GetUserAuthDetails(session.UserAuthId)
.ConvertAll(x => (IAuthTokens)x);
return true;
}
return false;
}
}
And from within my Janrain success callback code I call it so:
HostContext.ResolveService<UsernameOnlyAuthorizationService>().Post(new UsernameOnlyLoginRequest() {username = user.Username, returnUrl= returnUrl});
This seems to work nicely, however, I can't get it to remember my session across browser closes. I am hardcoding RememberMe = true - why is this not working?
I would do this by creating an internal service, which you can call from your MVC5 controller action, where you only require to pass the username of the user you have authenticated.
public class JanrainSuccessService : Service
{
public void CreateSessionFor(string username)
{
var repository = TryResolve<IAuthRepository>().AsUserAuthRepository(GetResolver());
var user = repository.GetUserAuthByUserName(username);
var session = GetSession();
session.PopulateWith(user);
session.IsAuthenticated = true;
session.UserAuthId = user.Id.ToString(CultureInfo.InvariantCulture);
session.ProviderOAuthAccess = repository.GetUserAuthDetails(session.UserAuthId).ConvertAll(x => (IAuthTokens)x);
}
}
The code in this method, is effectively the same could that is used by the CredentialsAuthProvider, but has the advantage of not requiring the password of the user. (See the TryAuthenticate method here for original code)
In your MVC5 controller action method you would need to call:
HostContext.ResolveService<JanrainSuccessService>().CreateSessionFor(user.user_id);
This assumes that you have a valid repository of users configured to match username's against.
You should update your code to be:
public class UsernameOnlyAuthorizationService : Service
{
public object Post(UsernameOnlyLoginRequest request)
{
var authProvider = new UsernameOnlyAuthProvider();
authProvider.Authenticate(this, GetSession(), new Authenticate()
{
UserName = request.username,
Password = "NotRelevant",
RememberMe = true
});
// Remember the session
base.Request.AddSessionOptions(SessionOptions.Permanent);
return HttpResult.Redirect(request.returnUrl);
}
}
public class UsernameOnlyAuthProvider : CredentialsAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
var authRepo = authService.TryResolve<IAuthRepository>().AsUserAuthRepository(authService.GetResolver());
ReferScienceDataContext db = authService.TryResolve<ReferScienceDataContext>();
var session = authService.GetSession();
var user = db.Users.FirstOrDefault(u => u.Username == userName);
if (user == null)
return false;
session.Id = user.Id.ToString();
session.UserName = user.Username;
session.FirstName = user.FirstName;
session.LastName = user.LastName;
session.IsAuthenticated = true;
session.UserAuthId = user.Id.ToString(CultureInfo.InvariantCulture);
session.ProviderOAuthAccess = authRepo.GetUserAuthDetails(session.UserAuthId).ConvertAll(x => (IAuthTokens)x);
return true;
}
}

Symfony2 get userID on kernel.controller event listener

Can someone help me to get userID in event listener from event.controller?
# EventListener
kernel.listener.corporation.manage:
class: Site\CorporationBundle\Event\SiteCorporationManageListener
arguments: ["#doctrine.orm.entity_manager", "#user.own.item", "#security.context"]
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onKernelRequest }
And listener class
use Doctrine\ORM\EntityManager;
use Site\MainBundle\Service\UserOwnItem;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\Security\Core\SecurityContext;
class SiteCorporationManageListener
{
private $oEntityManager = null;
private $oUserOwnItem = null;
private $oSecurityContext = null;
public function __construct(EntityManager $oEntityManager, UserOwnItem $oUserOwnItem, SecurityContext $oSecurityContext)
{
$this->oEntityManager = $oEntityManager;
$this->oUserOwnItem = $oUserOwnItem;
$this->oSecurityContext = $oSecurityContext;
}
public function onKernelRequest(FilterControllerEvent $event)
{
if (HttpKernelInterface::MASTER_REQUEST === $event->getRequestType()) {
$route = $event->getRequest()->get('_route');
$corporationID = $event->getRequest()->get('corporationID', null);
$userID = $this->oSecurityContext->getToken()->getUser()->getID();
//$userID = 3;
//var_dump($userID);
if (strstr($route, 'corporation')) {
if (!strstr($route, 'corporation_index')) {
$bUserOwn = $this->oUserOwnItem->setUserID($userID)->setItemType('corporation')->setItemID($corporationID)->userOwn();
//var_dump($bUserOwn);
}
}
}
}
}
I'll clean it later, i try different ways to do it, but even through container and security_context i cannot get userID. It brokes after getToken() method =.
At this example $userID is null... Even after getToken()->getUser() give me null...
FatalErrorException: Error: Call to a member function getUser() on a non-object in /home/dev/public_html/git.eve-ceo/src/Site/CorporationBundle/Event/SiteCorporationManageListener.php line 32
Help please.
Can get user id with this.
Include this.
use FOS\UserBundle\Model\UserInterface;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
Here you get userId
$user = $this->oSecurityContext->getToken()->getUser();
if (!is_object($user) || !$user instanceof UserInterface) {
throw new AccessDeniedException('You are not authorize to access this location.');
}
else{
$userID = $user->getId();
}
you must use checking of token like this:
if ($context->getToken() && $context->getToken()->getUser() !== 'anon.')
$user = $context->getToken()->getUser();
Basically you are not authenticated user.
I think the same, but in symfony if you put "var_dump($user)" before you are logged in, the result is: string 'anon.' (length=5).
Maybe you can use "is_object($user) (for me is better option)
like:
if ($securityContext->getToken() && is_object($securityContext->getToken()->getUser())){
For an update, service security.context is deprecated since Symfony 2.6.
So your new service declaration should looks like
# EventListener
kernel.listener.corporation.manage:
class: Site\CorporationBundle\Event\SiteCorporationManageListener
arguments: ["#doctrine.orm.entity_manager", "#user.own.item", "#security.token_storage"]
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onKernelRequest }
Construct would be like
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
...
public function __construct(EntityManager $oEntityManager, UserOwnItem $oUserOwnItem, TokenStorage $tokenStorage)
{
$this->oEntityManager = $oEntityManager;
$this->oUserOwnItem = $oUserOwnItem;
$this->tokenStorage = $tokenStorage;
}
Then getting the user would become
$user = $this->tokenStorage->getToken()->getUser();
source : http://symfony.com/blog/new-in-symfony-2-6-security-component-improvements
There is a similar issue, where I posted an answer solving it:
https://stackoverflow.com/a/49794146/2564552
In short: Your Listener on kernel.controller is called before the Token is initialized, so you have to play with priorities or (in newer Symfony versions) just use the (not yet documented) kernel.controller_arguments event.

Is OrganizationServiceProxy Connected?

What is the best way to tell if an OrganizationServiceProxy has successfully connected to CRM?
I am using GetEnumerator() on AccountSet as this fails if not connected.
/* Tries to connect to CRM and return false if failure - credentials arguments */
public bool Connect(string username, string password, string uri)
{
try
{
var cred = new ClientCredentials();
cred.UserName.UserName = username;
cred.UserName.Password = password;
service = new OrganizationServiceProxy(new Uri(uri), null, cred, null);
service.EnableProxyTypes(); // Allow LINQ early bound queries
linq = new Context(service);
/* This is where I need help */
var e = linq.AccountSet.GetEnumerator(); // this fails if not connected
}
catch
{
return false;
}
return true;
}
Service and Linq are private fields.
Context is the serviceContextName in crmsvcutil.exe.
I am in the habit of using the name "linq" for the Context object.
There must be a better way.
The simplest way is to execute a WhoAmIRequest, this because when you connect to CRM you need to provide valid credentials.
If the credentials are correct the WhoAmIRequest will return the current user GUID, if are not correct the request will fail.
So your code can be:
public bool Connect(string username, string password, string uri)
{
try
{
var cred = new ClientCredentials();
cred.UserName.UserName = username;
cred.UserName.Password = password;
service = new OrganizationServiceProxy(new Uri(uri), null, cred, null);
WhoAmIRequest request = new WhoAmIRequest();
WhoAmIResponse response = (WhoAmIResponse)service.Execute(request);
Guid userId = response.UserId;
}
catch
{
return false;
}
return true;
}

Obtaining Access token from refresh token using Google API

I have been using google+ api for .NET in my application.Using the example provided in this
site i am able to get the access token.
My problem is how to obtain the access token from the refresh token using OAuth 2.0.I haven't found any examples to get the access token from refresh token.
I have referred the [google+ API reference] but they have mentioned it using HTTP methods.2Please provide me some examples in C# using the methods provided by google+ APIs.
For the first time, you need to get the access token from the browser prompt and then save it in some store.
Check if the token is expired and then try to refresh it.
Code here :
private static IAuthorizationState GetAuthentication(NativeApplicationClient arg)
{
try
{
// Get the auth URL:
var config = new Configuration();
var calendarScope = Google.Apis.Util.Utilities.ConvertToString(CalendarService.Scopes.Calendar);
IAuthorizationState state = new AuthorizationState(new[] { calendarScope });
state.Callback = new Uri(NativeApplicationClient.OutOfBandCallbackUrl);
Uri authUri = arg.RequestUserAuthorization(state);
var authCode = String.Empty;
if (String.IsNullOrWhiteSpace(config.AccessToken) || config.AccessTokenExpirationTime < DateTime.Now || String.IsNullOrWhiteSpace(config.RefreshToken))
{
// Request authorization from the user (by opening a browser window):
Process.Start(authUri.ToString());
do
{
authCode = Prompt.ShowDialog("Test", "123");
} while (String.IsNullOrWhiteSpace(authCode));
state = arg.ProcessUserAuthorization(authCode, state);
}
else
{
state.AccessToken = config.AccessToken;
state.AccessTokenExpirationUtc = config.AccessTokenExpirationTime;
state.AccessTokenIssueDateUtc = config.AccessTokenIssueTime;
state.RefreshToken =config.RefreshToken ;
if (state.AccessTokenExpirationUtc < DateTime.Now)
{
var tokenRefreshed = arg.RefreshToken(state);
if (tokenRefreshed)
{
config.AccessToken = state.AccessToken;
config.AccessTokenExpirationTime = state.AccessTokenExpirationUtc;
config.AccessTokenIssueTime = state.AccessTokenIssueDateUtc;
config.RefreshToken = state.RefreshToken;
arg.ProcessUserAuthorization(authCode, state);
}
else
{
throw new ApplicationException("Unable to refresh the token.");
}
}
}
return state;
}
catch (System.Exception exp)
{
throw new ApplicationException("Failed to get authorisation from Google Calender.", exp);
}
}

Resources