Starting with Cyclos 4.2, using web services together with the right configuration, it is possible to add a Cyclos login form to an external website. The user types in his/hers Cyclos username and password in that form and, after a successful login, is redirected to Cyclos, where the session will be already valid, and the user can perform the operations as usual. After the user clicks logout, or his/hers session expires, the user is redirected back to the external website.
The following aspects should be considered:
It is needed to have an administrator whose group is granted the permission "Login users via web services". This is needed because the website will relay logins from users their clients to Cyclos.
The website needs to have that administrator's username and password configured in order to make the web services call. It is planned for Cyclos 4.3 the creation of access clients, which will allow using a separated key instead of the username / password.
It is a good practice to create a separated configuration for that administrator. That configuration should have an IP address whitelist for the web services channel. Doing that, no other server, even if the adminitrator username / password is known by someone else, will be able to perform such operations.
The Cyclos configuration for users needs the following settings:
Redirect login to URL: This is the URL of the external website which contains the login form. This is used to redirect the user when his session expires and a new login is needed, or when the user navigates directly to some URL in Cyclos (as guest) and then clicks "Login";
URL to redirect after logout: This is the URL where the user will be redirected after clicking "Logout" in Cyclos. It might be the same URL as the one for redirect login, but not necessarily.
Finally, the web service code needs to be created, and deployed to the website. Here is an example, which receives the username and password parameters, calls the web service to create a session for the user (passing his remote address), redirecting the user to Cyclos.
<?php // Configure Cyclos and obtain an instance of LoginService require_once 'configureCyclos.php'; $loginService = new Cyclos\LoginService(); // Set the parameters $params = new stdclass(); $params->user = array("principal" => $_POST['username']); $params->password = $_POST['password']; $params->remoteAddress = $_SERVER['REMOTE_ADDR']; // Perform the login try { $result = $loginService->loginUser($params); } catch (Cyclos\ConnectionException $e) { echo("Cyclos server couldn't be contacted"); die(); } catch (Cyclos\ServiceException $e) { switch ($e->errorCode) { case 'VALIDATION': echo("Missing username / password"); break; case 'LOGIN': echo("Invalid username / password"); break; case 'REMOTE_ADDRESS_BLOCKED': echo("Your access is blocked by exceeding invalid login attempts"); break; default: echo("Error while performing login: {$e->errorCode}"); break; } die(); } // Redirect the user to Cyclos with the returned session token header("Location: " . Cyclos\Configuration::getRootUrl() . "?sessionToken=" . $result->sessionToken); ?>
Important notes
In case there is a wrong configuration for the "Redirect login to URL" setting, it won't be possible anymore to login to Cyclos. In that case, if the configuration problem is within a network, it is possible to use a global administrator to login in global mode (using the <server-root>/global/login URL), then switch to the network and fix the configuration. If the configuration error is in global mode, you can use a special URL to prevent redirect: <server-root>/global/login!noRedirect=true . However, this flag only works in global mode, to prevent end-users from using it to bypass the redirect.
Users should never have username / password requested in a plain HTTP connection. Always use a secure (HTTPS) connection. Also, just having an iframe with the form on a secure page, where the iframe itself is displayed in a plain page would encrypt the traffic, but browsers won't show the page as secure. Users won't notice that page as secure, could refuse to provide credentials in such situation.
Creating an alternate frontend to Cyclos
It is possible to not only place a login form in an external website, but to create an entire fronted for users to interact with Cyclos. At first glimpse, this can be great, but consider the following:
It is a very big effort to create a frontend, as there are several Cyclos services involved, and it might not be clear without a deep analysis on the API which service / method / parameters should be used on each case.
The API will change. Even if we try not to break compatibility, it is possible that changes between 4.x to 4.y will contain (sometimes incompatible) changes to the API.
You will always have a limited subset of the functionality Cyclos offers. You may think that only the very basic features are needed, there will inevitably be the need for more features, and the custom frontend will need to grow. By using Cyclos standard web, all this comes automatically.
Neverthless, some (large) organizations might find it is better to provide their users with a single, integrated interface. In that case the application server of that interface will be the only one interacting with Cyclos (i.e, users won't directly browse the Cyclos interface). The application will relay web service calls to Cyclos in behalf of users.
To accomplish that, it is needed to first login users in the same way as explained in the previous section. However, after the login is complete, instead of redirecting users to Cyclos, the application needs to store the session token, and probably the user id (as some operations requires passing the logged user id) – both data received after logging in – in a session (in the interface application server). Then, the next web service requests should be sent using that session token and client remote address, instead of the administrator credentials. The way of passing that data depends on the web service access type being used:
Java clients: Create another HttpServiceFactory, using a stateful HttpServiceInvocationData. Here is an example:
import java.util.List;
import org.cyclos.model.access.LoggedOutException;
import org.cyclos.model.access.channels.BuiltInChannel;
import org.cyclos.model.access.login.UserAuthVO;
import org.cyclos.model.banking.accounts.AccountWithStatusVO;
import org.cyclos.model.users.users.UserLocatorVO;
import org.cyclos.model.users.users.UserLoginDTO;
import org.cyclos.model.users.users.UserLoginResult;
import org.cyclos.server.utils.HttpServiceFactory;
import org.cyclos.server.utils.HttpServiceInvocationData;
import org.cyclos.services.access.LoginService;
import org.cyclos.services.banking.AccountService;
/**
* Cyclos web service example: logs-in a user via web services.
* This is useful when creating an alternative front-end for Cyclos.
*/
public class LoginUser {
public static void main(String[] args) throws Exception {
// This LoginService has the administrator credentials
LoginService LoginService = Cyclos.getServiceFactory().getProxy(LoginService.class);
// Another option is to use an access client to connect with the
// server (for the admin)
// To make it works you must:
// 1- create an access client
// 2- assign it to the admin (to obtain the activation code)
// 3- activate it making a HTTP POST to the server using this url:
// ROOT_URL/activate-access-client containing only the activation code
// as the body
// 4- put the token returned from the servlet as the parameter of the
// HttpServiceInvocationData.accessClient(...) method
// 5- comment the first line (that using user and password and
// uncomment the following two sentences
// HttpServiceInvocationData adminSessionInvocationData =
// HttpServiceInvocationData
// .accessClient("put_the_token_here");
// LoginService LoginService = Cyclos.getServiceFactory(
// adminSessionInvocationData).getProxy(LoginService.class);
String remoteAddress = "192.168.1.200";
// Set the login parameters
UserLoginDTO params = new UserLoginDTO();
UserLocatorVO locator = new UserLocatorVO(UserLocatorVO.PRINCIPAL, "c1");
params.setUser(locator);
params.setPassword("1234");
params.setRemoteAddress(remoteAddress);
params.setChannel(BuiltInChannel.MAIN.getInternalName());
// Login the user
UserLoginResult result = LoginService.loginUser(params);
UserAuthVO userAuth = result.getUser();
String sessionToken = result.getSessionToken();
System.out.println("Logged-in '" + userAuth.getUser().getDisplay()
+ "' with session token = " + sessionToken);
// Do something as user. As the session token is only valid per ip
// address, we need to pass-in the client ip address again
HttpServiceInvocationData sessionInvocationData =
HttpServiceInvocationData.stateful(sessionToken, remoteAddress);
// The services acquired by the following factory will carry on the
// user session data
HttpServiceFactory userFactory = Cyclos.getServiceFactory(sessionInvocationData);
AccountService accountService = userFactory.getProxy(AccountService.class);
List<AccountWithStatusVO> accounts =
accountService.getAccountsSummary(userAuth.getUser(), null);
for (AccountWithStatusVO account : accounts) {
System.out.println(account.getType()
+ ", balance: " + account.getStatus().getBalance());
}
// Logout. There are 2 possibilities:
// - Logout as administrator:
LoginService.logoutUser(sessionToken);
// - OR logout as own user:
try {
userFactory.getProxy(LoginService.class).logout();
} catch (LoggedOutException e) {
// already logged out
}
}
}
PHP clients: In the configuration file, instead of calling Cyclos\Configuration::setAuthentication($username, $password), call the following: Cyclos\Configuration::setSessionToken($sessionToken) and Cyclos\Configuration::setForwardRemoteAddress(true), which will automatically send the $_SERVER['REMOTE_ADDR'] value on requests.
WEB-RPC: If sending JSON requests directly, instead of passing the Authentication header with the username / password, pass the following headers: Session-Token and Remote-Address.