External identity providers

From Cyclos4 Wiki
Jump to: navigation, search

Available from Cyclos version 4.13

Introduction

It is common nowadays for websites to relay the user authentication to external providers, such as Google and Facebook. These are trusted parties that contain user information. The same way as Cyclos has implemented in version 4.13 the OpenID Connect / OAuth protocol, which allow other services to “login with Cyclos”, starting with 4.13 Cyclos also allows users to login using their social network identities.

This works as follows:

  1. In the login page, a “Continue with <Provider>” button is shown in the login page for each registered provider;
  2. When the user clicks it, a popup with a page in the external provider is opened. This page will ask the user consent.
  3. Once the user authorizes, the popup is closed and Cyclos reads the user email (from the third party?). If a matching user exists in Cyclos with that e-mail a session is created for that user, and the user is automatically logged in.
If there is no matching email is found, a message is shown. The idea is that if no match is found, the user we be asked to login with their Cyclos credentials. The external provider id will be stored for that user. So, the next time there will be a match by id (not e-mail).

Note: Cyclos can also act as an identification provider for third party systems. This is explained in this wiki page.

Configuring Cyclos

Administrators with System configuration manage permission will be able to create identity providers in the System > System configuration > Identity providers page. There are a few predefined providers which are supported out-of-the-box: Google, Facebook, Twitter, Linked-In, Microsoft and Yahoo. There is also support for generic OpenID Connect servers (such as another Cyclos instance). Each provider will require a client id and secret. These should be obtained from the providers directly. See instructions below for each one.

After creating the providers, the administrator should enable them in the channel configuration page: System > System configuration > Configurations > [Configuration] > Channels > Main web > Identity providers.

Getting the client id and secret on each provider

Each provider offers a different way to configure the allowed clients and permissions. However, there is a common set of concepts: A client is an application that has an id and secret, as well as a set of allowed permissions and a set of allowed redirect URLs. The redirect URL can be obtained in the Identity provider details page, and will always be: <cyclos-root>/identity/callback.

Other common concepts managed by providers is the URL of the privacy policy page, as well as the URL for the terms of service. For production systems, prepare such pages, as they will be linked from the consent pages of the identity providers.

Google

  1. Go to the developers console: https://console.developers.google.com/
  2. In the OAuth consent screen, setup the fields, such as Application name, logo, url, authorized domains, etc. The scopes need to be set to the default (openid, profile, email).
    Note: Google doesn’t allow localhost. In case you’re running a local instance, use the lvh.me domain, which always point to the local IP address
  3. In the Credentials menu, create a new one of type OAuth client ID. Select application type Web application, give it a name and set the corresponding redirect URL.
  4. After the credential is created, it will show you the client id and client secret that should be configured in Cyclos
  5. Once you have tested the setup, submit the OAuth consent screen for verification. The same page has an explanation on the process.

Facebook

  1. Go to the developers page: https://developers.facebook.com/ and create an account if you hav’nt one.
  2. In the top right, there’s a menu My applications. Create a new one, filling up the required data
  3. In the application details, the menu Settings > Basic has the Application ID and Secret key - these should be set in the identity provider page in Cyclos.
  4. In the left menu click on ‘Products’ and chose the option 'Facebook login'
  5. Now facebook login should appear in the left menu with two submenus (settings and quick start). Select 'settings'
  6. Make sure the options Client OAuth Login and Web OAuth login are enabled. (usually enabled by default).
  7. Put the Callback URL from the identity provider page in the field: Valid OAuth Redirect URIs. (make sure to hit enter and save the page)
  8. Enable the application with the switch in the top bar. There are some requirements for this. Follow the presented instructions.

Twitter

  1. Go to the developer page: https://developer.twitter.com/apps
  2. Create a new app, filling in all required fields. Also check the Enable Sign in with Twitter checkbox. Also set in the Callback URLs
  3. Once saved, in the Keys and tokens tab, you will find the Consumer API keys: the first one is the client ID and the second, the client secret
  4. In the Permissions tab, set the access permissions to Read-only, and in the additional permissions, Request email address from users.

Microsoft

  1. Go to the Azure portal at https://portal.azure.com/
  2. In Azure Services, search for Azure Active Directory
  3. In the left menu, click on Manage > Application registry
  4. Register a new one in the toolbar, New registry
  5. Set a name. In the supported account type fields, select the last option - Accounts in any directory and personal Microsoft accounts
  6. In the application general view, you will find the Client application ID. That one is the client ID to set in Cyclos
  7. In the side menu, under Manage > Authentication, set the Redirect URIs to the URL in Cyclos, of type Web
  8. In the side menu, under Manage > Certificates and secrets, create a new client secret. Set a desired expiration period (or never) and create it. This one is the client secret to use.
    Note: Be careful that the secret value will only be displayed the first time it is created.
  9. In the side menu, under Manage > API permissions, make sure you have Microsoft Graph with the following 3 permissions: openid, profile and email

LinkedIn

  1. Go to the developers site at https://www.linkedin.com/developers/
  2. In the top bar there’s the My apps menu. Create a new one.
  3. Fill in all the fields. You will need a company pre-registered in Linked-in. On the products, the default, Sign In with LinkedIn
  4. After saving the app, in the Auth tab, there are the client ID and secret.
  5. Still in the Auth tab, there’s the Permissions, which should include these: r_emailaddress and r_liteprofile. Also, in the OAuth 2.0 setings below, set the redirect URL.
  6. Once everything is ok, go back to the Settings tab and verify that the application belongs to the company, following the indicated process.

Yahoo

  1. Go to the developers site at https://developer.yahoo.com/
  2. From the top bar menu, find the Apps entry, and create a new app
  3. Fill in the required fields. The application type is Web application. Set the Redirect URI and add on the API Permissions, Profiles (Social Directory). As the e-mail address is considered a private information, you will need Read/Write Public and Private permission.
  4. Then, in the client details page, there are both Client ID and Client Secret

Generic OpenID Connect provider

  1. Consult the documentation of the provider. In Cyclos, besides the client id and secret, you will need to set the discovery URL (generally https://provider.domain/.well-known/openid-configuration), in which the server returns all supported capability.
  2. For example, to use another Cyclos instance, create a new client as administrator in Users > OpenID Connect / OAuth2. Then set the redirect URL given from the client Cyclos instance in the Redirect URLs, and set the client id / secret from the provider
  3. For users to authenticate, they need the OpenID Connect / OAuth2 channel enabled

Note: A page document regarding configuring Cyclos as an identity provider can be found here.

Using identity providers together with the REST API

The identity providers operations cannot be fully achieved using solely the REST API because it requires the end user interaction. The general flow for such operations is:

  1. The frontend prepares the request in Cyclos using the corresponding REST operation. On the result there will be an URL for the next step (the one to use in the popup).
  2. The frontend subscribes to the push notification event type identityProviderCallback to the specific request id (if guest). If logged-in and the frontend uses push notifications already, just add that event type to the regular registration
  3. The frontend opens a popup with the URL previously returned.
  4. The popup displays the identity provider consent page for this operation. Generally only basic profile information and e-mail access are needed.
    • There may be variations: the provider may not show a login form if a user is already logged-in. Also, if the user has already consented that operation, the provider will probably silently redirect the user back to Cyclos without displaying anything.
  5. The provider redirects the user's popup back to Cyclos.
  6. Cyclos reads the profile information returned from the provider and sends a push notification event with the callback result. The popup and closes itself.
  7. The frontend proceeds with the operation by reading the object passed in the push notificaton event.

There are currently 3 possible actions with identity providers:

Here is a brief explanation of each action:

Login with an external provider

  1. The available identity providers for login are returned in the DataForLogin model.
  2. For each provider, the frontend should show a 'Continue with <provider>' button. The text, background and border colors, as well as the image are returned in the IdentityProvider model.
  3. When the user clicks one of the buttons, the frontend calls the REST operation POST /api/identity-providers/<provider-internal-name>/login. The result contains the requestId and the URL to proceed.
  4. The frontend subscribes to push notification event type identityProviderCallback passing in the identityProviderRequestId.
  5. The frontend opens a browser popup with the returned URL.
  6. The identity provider presents the operation for the user to consent.
  7. After the user consents, the IdentityProviderCallbackResult model is sent via push notification and the popup closes itself.
  8. The IdentityProviderCallbackResult has the following properties which are relevant for the login operation:
    • status: The operation status, as string. The relevant values for the login operation are:
      • loginLink: The user was successfully logged-in. There was a previous link between the user and the provider.
      • loginEmail: The user was successfully logged-in. There was previously no link between the user and the provider, but the user in Cyclos was found using the e-mail address returned by the provider.
      • loginNoMatch: There was no user previously linked to this provider and no user was matched by e-mail. In this case, the user will have to login using his regular username and password. However, the frontend should store the result value of the Id property and pass it in the login REST method (POST /api/auth/session?identityProviderRequestId=<requestId>). By doing so, the link between the authenticated user and the provider will be immediately created. The next time, the login will work directly.
      • loginNoEmail: Similar to loginNoMatch, but in this case the provider didn't return an e-mail address (because the user has no registered email, has denied sharing their e-mail, etc). The requestId property is also returned, so, if the frontend passes it on the regular login, the link between the user and the provider will be created.
      • denied: The user has denied the consent. Generally nothing should be done, as the popup will automatically close.
      • error: There was an error. A message MAY be available in the errorMessage property. If not, a generic message should be displayed to the user.
    • sessionToken: If the login succeeded, this is the session token that should be stored and passed in the subsequent REST requests via the Session-Token header. It is, however, recommended that this session token is replaced by using the POST /api/auth/session/replace/{session-token}, so a new token will be generated and, if desired, cookies will also be set. This property is returned when status is either loginLink or loginEmail.
    • requestId: A state that should be stored and passed in on the next login to automatically link the user and the provider. Is returned when status is either loginNoMatch or loginNoEmail.
    • errorMessage: A message to be displayed when status is error. If no message is returned for the error, a generic message should be displayed to the user.

Register with an identity provider

The registration with identity provider can be enabled / disabled in the Cyclos channel configuration. When enabled, there are 2 modes:

  • Try to automatically register the user: In this case, Cyclos will attempt to register the user with the fields read from the identity provider. If successful, the user will be automatically logged-in. If there are some registration agreements, they will be presented to the user, which must accept in order to use the system. Also, the login password can be set later on. However, if some validation fails, either because there are additional required fields, or because the e-mail / login name already exists, the profile data read from the provider is returned to the callback function (which should fill-in the registration form with it). This option allows a very quick registration, raising the chance that users will engage with the network.
  • Fill-in the registration form: No registration is automatically attempted. The profile data which was returned from the provider is handled over to the callback function, and the frontend should fill-in the registration form with it. This option should be used when there are legal concerns that users must first accept any agreements before being actually registered in the system.

Registering with a provider presents the following benefits:

  • Users don't have to type in repeated data
  • There's no need to fill in a CAPTCHA, as the identity provider requestId already prevents robots from mass registrations
  • The user is already linked to the provider, so, regardless of changing the e-mail address, the login will always work for that user / provider.

This is the flow for the registration with an identity provider to be used in external frontends:

  1. The available identity providers for registration are returned in the UserDataForNew model in public registrations (not when it is an administrator / broker registering users).
  2. For each provider, the frontend should show a 'Continue with <provider>' button. The text, background and border colors, as well as the image are returned in the IdentityProvider model.
  3. When the user clicks one of the buttons, the frontend calls the REST operation POST /api/identity-providers/<provider-internal-name>/register?group=<group-id-or-internal-name>. The result contains the requestId and the URL to proceed.
  4. The frontend subscribes to push notification event type identityProviderCallback passing in the identityProviderRequestId.
  5. The frontend opens a browser popup with the returned URL.
  6. The identity provider presents the operation for the user to consent.
  7. After the user consents, the IdentityProviderCallbackResult model is sent via push notification and the popup closes itself.
  8. The IdentityProviderCallbackResult has the following properties which are relevant for the registration operation:
    • status: The operation status, as string. The relevant values for the registration operation are:
      • registrationData: The user profile data read from the identity provider is returned. The frontend should fill-in the registration form with it. When submitting the registration, the UserNew.identityProviderRequestId should be set to this value.
      • registrationDone: The direct registration was enabled and succeeded. If this status is returned, the user is already logged-in, and the session token is returned in the sessionToken property.
      • denied: The user has denied the consent. Generally nothing should be done, as the popup will automatically close.
      • error: There was an error. A message MAY be available in the errorMessage property. If not, a generic message should be displayed to the user.
    • name: The display name of the user read from the identity provider.
    • username: The login name of the user read from the identity provider, if any.
    • email: The e-mail of the user read from the identity provider, if any.
    • mobilePhone: The mobile phone number of the user read from the identity provider, if any.
    • landLinePhone: The land-line phone number of the user read from the identity provider, if any.
    • landLineExtension: The land-line phone extension of the user read from the identity provider, if any.
    • image: The Image object with the image read from the provider, if any.
    • customValues: An object whose keys are custom field internal names and values are the string representation of the value. It may or may not be returned, an the object may also be empty. Most providers don't support custom fields, with the exception of Facebook, which allows website. For policy reasons we can't read birthday or gender from Facebook. See https://developers.facebook.com/docs/facebook-login/permissions/ for more details.
    • sessionToken: If the login succeeded, this is the session token that should be stored and passed in the subsequent REST requests via the Session-Token header. It is, however, recommended that this session token is replaced by using the POST /api/auth/session/replace/{session-token}, so a new token will be generated and, if desired, cookies will also be set. This property is returned if status is registrationDone.
    • requestId: The operation identifier that should be stored and passed in when the user submits the registration form. Is returned when status is registrationData.
    • errorMessage: A message to be displayed when status is error. If no message is returned for the error, a generic message should be displayed to the user.

Link an identity provider to the logged user

This can be done for users authenticated via session token (must be stateful, other methods won't work), if thefrontend has can manage the identity provider links for the logged user. This would be the flow:

  1. The frontend should call the GET /api/self/identity-providers/list-data operation to get the list of available providers, together with their statuses.
  2. For each provider, whose status is not linked, the frontend should show the user an option to link
  3. When the user clicks on the option to link a provider, the frontend calls the REST operation POST /api/identity-providers/<provider-internal-name>/link. The result contains the requestId and the URL to proceed.
  4. The frontend subscribes to push notification event type identityProviderCallback passing in the identityProviderRequestId. If the frontend already has a push notifications subscription, it can just add the identityProviderCallback event type to the list of subscribed types and check that the received requestId matches the one previously returned.
  5. The frontend opens a browser popup with the returned URL.
  6. The identity provider presents the operation for the user to consent.
  7. After the user consents, the IdentityProviderCallbackResult model is sent via push notification and the popup closes itself.
  8. The IdentityProviderCallbackResult has the following properties which are relevant for the link operation:
    • status: The operation status, as string. The relevant values for the link operation are:
      • linked: The identity provider was successfully linked to the user.
      • denied: The user has denied the consent. Generally nothing should be done, as the popup will automatically close.
      • error: There was an error. A message MAY be available in the errorMessage property. If not, a generic message should be displayed to the user.
    • errorMessage: A message to be displayed when status is error. If no message is returned for the error, a generic message should be displayed to the user.