Setting up these identity providers involves two main steps:
- Setting Up the Identity Providers - Configuration of the loginAuthSettings.json file to specify the IdPs SGS should recognize and interface with. Each of these properties must be accurately configured to ensure successful integration and communication between SGS and the selected IdPs.
- Managing the User Lists - The approach to managing these lists varies between enterprise and social IdPs. For enterprise IdPs, which are typically used within organizations to manage employee identities, scripts are utilized to synchronize the user list with SGS. This process includes connecting to the enterprise IdP, retrieving user information (ensuring that the user's email address is set as their username), and using the SGS API to create or update user accounts accordingly. For social IdPs like Facebook and Google, management involves developing or implementing checker applications that act as intermediaries, handling the authentication data from the social IdPs and determining whether a user logging in already has an SGS account. Depending on the organization's policies, the checker application might automatically create a new user account in SGS, assign specific permissions, or deny access if the user does not exist.
Setting Up the Identity Providers
- Locate the loginAuthSettings.json file in the SharedConfigurations subdirectory within the SharedDataPath folder defined in your deployment’s settings file. This file includes a property named identityProviders, which is an array composed of objects. Each object represents an identity provider. More about: SGS Deployment Settings >
- Windows: appsettings.json
- Docker: docker-compose.yaml
- Kubernetes: deployment.yaml
- For each identity provider you want to add, uncomment the lines corresponding to its properties within the identityProviders comma-delimited array. The forceIdentityProvider property can be used to enforce a specific identity provider by setting its value to the name of one of the providers listed in the IdentityProviders array. Leave it as an empty string ("") if you don't want to enforce a specific provider.
{
"identityProviders": [
{}
],
"forceIdentityProvider": ""
}
Properties:
| Name | Description |
| name | Name of IdP, e.g., "Facebook". |
| loginIcon | Path to the icon that should be used for the IdP button in the login dialog. |
| loginUrl | URL of the IdP that users will be redirected to in order to log in. This information is obtained from the IdP. |
| authTokenURL | URL endpoint at which you will receive access tokens from the IdP to authenticate and authorize users for your server. This information is obtained from the IdP. |
| authTokenURLRequestBody |
Request body to be sent (POST) within the "authTokenURL" request:
These are obtained from the IdP:
Example: client_id=XXXXXX&client_secret=YYYYYY&grant_type=authorization_code&redirect_uri=https://cloud.skylineglobe.com/oauth/redirect |
| getUserInfoURL |
URL for retrieving the user profile information (e.g., user name, email). This information is obtained from the IdP. Make sure that the user profile information returned by the IdP uses the user's email address as the username. This endpoint requires the People API to be enabled in the Google Cloud Console. Example: "getUserInfoURL": "https://people.googleapis.com/v1/people/me?personFields=names,emailAddresses" If the People API is not enabled, use instead: "getUserInfoURL": "https://www.googleapis.com/oauth2/v2/userinfo"
|
3. After creating/modifying the configuration file, restart SGS for updates to take effect.
Examples
Detailed examples for IdPs like Cognito, Microsoft, Google, and Facebook are provided below. For a detailed example JSON illustrating the setup for all these identity providers, see the attachment to this article.
Cognito
Properties/Attributes:
- "name": String with name of IdP
- "loginIcon": URL to your IdP logo
-
"loginUrl": Base URL: Obtained from your IdP application + /login? (See screenshot below)
- redirect_uri: URL to which the IdP server should redirect users after authentication.
- response_type: Set to =code
-
client_id: Obtained from your IdP application (See screenshot below)
- "authTokenURL": Obtained from your IdP application + /oauth2/token (See screenshot below)
-
"authTokenURLRequestBody":
- client_id: Obtained from your IdP application (See screenshot below)
- client_secret: Obtained from your IdP application (See screenshot below)
- grant_type: Set to =authorization_code
- redirect_uri: URL to which the IdP server should redirect users after authentication.
- "getUserInfoURL": Obtained from your IdP application + /oauth2/userInfo (See screenshot below)
{
"name": "Cognito",
"loginIcon": "https://mysite/logos/cognito256x219.png",
"loginUrl": "https://test.auth.us-east-1.amazoncognito.com/login?redirect_uri=https://mysite.com/oauth/redirect&response_type=code&client_id=6d7qubrlgjdhersksm8ol1pc2c",
"authTokenURL": "https://test.auth.us-east-1.amazoncognito.com/oauth2/token",
"authTokenURLRequestBody": "client_id=6d7qubrlgjdhersksm8ol1pc2c&client_secret=1mtm9ftuukbm8oqp2oc9l73oaai92hulnl4kvib9nisrl91c8u81&grant_type=authorization_code&redirect_uri=https://mysite/oauth/redirect",
"getUserInfoURL": "https://test.auth.us-east-1.amazoncognito.com/oauth2/userInfo"
}Microsoft
Properties/Attributes:
- "name": String with name of IdP
- "loginIcon": URL to your IdP logo
-
"loginUrl": Set to "https://login.live.com/oauth20_authorize.srf?"
-
client_id: Obtained from your IdP application (See screenshot below)
- redirect_uri: URL to which the IdP server should redirect users after authentication.
- scope: Set to =wl.emails
- response_type: Set to =code
-
client_id: Obtained from your IdP application (See screenshot below)
- "authTokenURL": Set to "https://login.live.com/oauth20_token.srf"
-
"authTokenURLRequestBody":
- client_id: Obtained from your IdP application (See screenshot below)
- client_secret: Obtained from your IdP application (See screenshot below)
- grant_type: Set to =authorization_code
- redirect_uri: URL to which the IdP server should redirect users after authentication.
- "getUserInfoURL": Set to "https://apis.live.net/v5.0/me"
Personal
{
"name": "Microsoft",
"loginIcon": "https://mysite/logos/Microsoft256x219.png",
"loginUrl": "https://login.live.com/oauth20_authorize.srf?client_id=aa404e51-8760-4441-b0fe-424cb7559fea&redirect_uri=https://mysite/oauth/redirect&scope=wl.emails&response_type=code",
"authTokenURL": "https://login.live.com/oauth20_token.srf",
"authTokenURLRequestBody": "client_id=aa404e51-8760-4441-b0fe-424cb7559fea&client_secret=iQL8Q~vtqAhznD3VvPe9~Y3xCht1pNAEOD8Yda-~&grant_type=authorization_code&redirect_uri=https://mysite/oauth/redirect",
"getUserInfoURL": "https://apis.live.net/v5.0/me"
}
365
{
"name": "Microsoft",
"loginIcon": "https://cloud.skylineglobe.com/images/MS.256x256.png",
"loginUrl": "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=5d1a0c8d-03ee-a116-a77D-d4a701f34e21&redirect_uri=https://cloud.skylineglobe.com/oauth/redirect&scope=openid+profile+email&response_type=code",
"authTokenURL": "https://login.microsoftonline.com/common/oauth2/v2.0/token",
"authTokenURLRequestBody": "client_id=5d1a0c8d-03ee-a116-a77D-d4a701f34e21&client_secret=ABCDE~wKEHkPnYMWH36~2gETxiEH7H-VatjJLbxY&grant_type=authorization_code&redirect_uri=https://cloud.skylineglobe.com/oauth/redirect",
"getUserInfoURL": "https://graph.microsoft.com/oidc/userinfo"
}
Properties/Attributes:
- "name": String with name of IdP
- "loginIcon": URL to your IdP logo
-
"loginUrl": Set to "https://accounts.google.com/o/oauth2/v2/auth?"
-
client_id: Obtained from your IdP application (See screenshot below)
- redirect_uri: URL to which the IdP server should redirect users after authentication.
- scope: Set to =openid email profile
- response_type: Set to =code
-
client_id: Obtained from your IdP application (See screenshot below)
- "authTokenURL": Set to "https://oauth2.googleapis.com/token"
-
"authTokenURLRequestBody":
- client_id: Obtained from your IdP application (See screenshot below)
- client_secret: Obtained from your IdP application (See screenshot below)
- grant_type: Set to =authorization_code
- redirect_uri: URL to which the IdP server should redirect users after authentication.
-
"getUserInfoURL": Set to "https://people.googleapis.com/v1/people/me?"
- personFields: Set to =names,emailAddresses
{
"name": "Google",
"loginIcon": "https://mysite/logos/google.256x256.png",
"loginUrl": "https://accounts.google.com/o/oauth2/v2/auth?client_id=585398732296-6c6h8kl1s4er1vl7bkgqd0bhlp944of7.apps.googleusercontent.com&redirect_uri=https%3A%2F%2Fmysite%2Fsg%2Foauth%2Fredirect&scope=openid%20email%20profile&response_type=code",
"authTokenURL": "https://oauth2.googleapis.com/token",
"authTokenURLRequestBody": "client_id=585398732296-6c6h8kl1s4er1vl7bkgqd0bhlp944of7.apps.googleusercontent.com&client_secret=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&grant_type=authorization_code&redirect_uri=https://mysite/oauth/redirect",
"getUserInfoURL": "https://people.googleapis.com/v1/people/me?personFields=names,emailAddresses"
}
Properties/Attributes:
- "name": String with name of IdP
- "loginIcon": URL to your IdP logo
-
"loginUrl": Set to "https://www.facebook.com/v14.0/dialog/oauth?"
- client_id: Obtained from your IdP application (See screenshot below)
-
redirect_uri: URL to which the IdP server should redirect users after authentication.
- scope: Set to =email
- "authTokenURL": Set to "https://graph.facebook.com/v14.0/oauth/access_token"
-
"authTokenURLRequestBody":
- client_id: Obtained from your IdP application (See screenshot below)
- client_secret: Obtained from your IdP application (See screenshot below)
- grant_type: Set to =authorization_code
- redirect_uri: URL to which the IdP server should redirect users after authentication.
-
"getUserInfoURL": Set to "https://graph.facebook.com/v14.0/me?"
- fields: Set to =email
{
"name": "Facebook",
"loginIcon": "https://mysite/logos/facebook.256x256.png",
"loginUrl": "https://www.facebook.com/v14.0/dialog/oauth?client_id=616496163768891&redirect_uri=https://mysite/oauth/redirect&scope=email",
"authTokenURL": "https://graph.facebook.com/v14.0/oauth/access_token",
"authTokenURLRequestBody": "client_id=616496163768891&client_secret=9ee6cc364bdad30ed4a0d75487c21465&grant_type=authorization_code&redirect_uri=https://mysite/oauth/redirect",
"getUserInfoURL": "https://graph.facebook.com/v14.0/me?fields=email"
}
Okta
Properties/Attributes:
- "name": String with name of IdP
- "loginIcon": URL to your IdP logo
-
"loginUrl": Base URL: Obtained from your IdP application + /oauth2/v1/authorize? (See screenshot below)
- response_type: Set to =code
- redirect_uri: URL to which the IdP server should redirect users after authentication.
-
client_id: Obtained from your IdP application (See screenshot below)
- "authTokenURL": Obtained from your IdP application + /oauth2/v1/token (See screenshot below)
-
"authTokenURLRequestBody":
- client_id: Obtained from your IdP application (See screenshot below)
- client_secret: Obtained from your IdP application (See screenshot below)
- grant_type: Set to =authorization_code
- redirect_uri: URL to which the IdP server should redirect users after authentication.
- "getUserInfoURL": Obtained from your IdP application + /oauth2/v1/userinfo (See screenshot below)
{
"name": "Okta",
"loginIcon": "https://mysite/logos/images/okta.256x256.png",
"loginUrl": "https://trial-3036531.okta.com/oauth2/v1/authorize?response_type=code&client_id=0oa5m587i2b0j2T7X697&redirect_uri=https://mysite/oauth/redirect&scope=openid%20email%20profile",
"authTokenURL": "https://trial-3036531.okta.com/oauth2/v1/token",
"authTokenURLRequestBody": "client_id=0oa5m587i2b0j2T7X697&client_secret=81YfiITWv6nxXXOn9Jf2QOza2Pzq0DSnnh7NMJna&grant_type=authorization_code&redirect_uri=https://mysite/oauth/redirect",
"getUserInfoURL": "https://trial-3036531.okta.com/oauth2/v1/userinfo"
}
IdentityServer4
IdentityServer4 is an OpenID Connect and OAuth 2.0 framework specifically designed for ASP.NET Core applications. It provides centralized authentication logic, single sign-on capabilities, access control for APIs, and support for external identity providers like Azure Active Directory and Google.
Properties/Attributes:
- "name": String with name of IdP
- "loginIcon": URL to your IdP logo
-
"loginUrl": Your server URL + /authorize/
- client_id: As was set in your IdentityServer4 application (See screenshot below)
- redirect_uri: URL to which the IdP server should redirect users after authentication.
- scope: Set to =openid%20email%20profile
- response_type: Set to =code
- "authTokenURL": Your server URL + /token
-
"authTokenURLRequestBody":
- client_id: As was set in your IdentityServer4 application (See screenshot below)
- client_secret: As was set in your IdentityServer4 application (See screenshot below)
- grant_type: Set to =authorization_code
- redirect_uri: URL to which the IdP server should redirect users after authentication.
- "getUserInfoURL": Your server URL + /userinfo
{
"name": "IdentityServer4",
"loginIcon": "https://mysite/logos/ids4.png",
"loginUrl": "https://myserver/myApplication/authorize?client_id=MyClientID&redirect_uri=https://mysite/oauth/redirect&scope=openid%20email%20profile&response_type=code",
"authTokenURL": "https://myserver/myApplication/token",
"authTokenURLRequestBody": "client_id=MyClientID&client_secret=MyClientSecret&grant_type=authorization_code&redirect_uri=https://mysite/oauth/redirect",
"getUserInfoURL": "https://myserver/myApplication/userinfo"
}
Managing the User Lists
SGS must have a corresponding user account for each person who authenticates through an identity provider. Organizations can manage these accounts either on demand using a checker application or in bulk using a synchronization script.
A checker application acts as middleware between the IdP and SGS. It uses the SGS API to determine whether a user signing in through the IdP already has an SGS account. If the account exists, the application continues the login. If it does not, the application applies the organization’s access policy, such as creating the account, assigning permissions, or denying access.
Organizations can also use a custom synchronization script to provision multiple user accounts in advance. For enterprise IdPs, the script generally retrieves names, email addresses, and roles directly from the organization’s directory. For social IdPs, this information may come from another organizational database. The script then uses the SGS API to create or update the corresponding SGS accounts. Organizations can run it periodically to reflect changes such as new hires, role changes, and departures.
Sample Checker Application
The following sample code can serve as the basis for a checker application. It checks whether the user has an SGS account and creates the account with the configured group and permissions if necessary. Adapt the sample to implement your organization’s access policy.
<html>
<head></head>
<body onload = "init()">
<script language= "JavaScript">
// Credentials used ONLY to authenticate the API calls below (ConnectSG/GraphQL) as an
// Administrator/SuperAdmin service account. These must NOT be the identity returned by
// the IdP - the actual signed-in user is checked/created as "userName" below instead.
var adminUserName = "";
var adminPassword = "";
//// These parameters should be obtained from the returned IDP response (e.g. from the query string or id_token claims). Fill them in with your own integration.
var server = ""; // e.g. "https://yourserver.com:5000/" - fill in your SGS server URL, including trailing slash.
var siteName = ""; // e.g. "Default" - fill in your SGS site name.
// Dummy password assigned to a brand-new local SGS account. CreateUser requires a value
// here, but no one ever logs in with it - users only ever authenticate via SSO, which
// ends in a redirect, so this can just stay a fixed placeholder string.
var newUserPassword = "dummyPassword1!";
// The query string retrieved from the IdP response, e.g. "?state=[ENCODED_STATE]&code=[AUTHORIZATION_CODE]&scope=[SCOPES]".
var param = ""; // fill in with the query string retrieved from the IdP response.
// The fields below identify the signed-in user and the access policy to apply. This
// sample does not parse them out of the IdP response itself - fill them in with
// whatever your own integration extracts from it (e.g. from the id_token claims).
var userName = ""; // the signed-in user to check/create in SGS.
var Role = ""; // Viewer | Publisher | SiteAdmin | SuperAdmin
var groupName = ""; // Optional: exact name of the group new users should be added to.
// Leave empty to use the site's first available group.
function init(){
// connect with admin credentials to implement SGS API as Super Admin \ Site Admin: check if user exist and create user \ deny access
// NOTE: the login handshake still uses the legacy REST API. It returns the SGAuth
// cookie that authenticates the GraphQL requests made below (the browser attaches
// it automatically since GraphQL is called on the same origin).
fetch(server + "/" + siteName + "/ConnectSG", {
"body": "{\n \"request\": \"login\",\n \"username\": \"" + adminUserName + "\",\n \"password\": \"" + adminPassword + "\"\n , \"isPersistent\": true\n}",
"method": "POST",
"credentials": "include",}).then((response) => response.json()).then((response) =>{if(response.result == "success") checkUser(userName);});
}
// Sends a GraphQL request to the single, site-agnostic /graphql endpoint.
// The site context comes from the SGAuth cookie set by ConnectSG, not from the URL.
function graphQL(query, variables){
debugger;
return fetch(server + "/graphql", {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify({ query: query, variables: variables }),
}).then((response) => response.json()).then((result) => {
if (result.errors && result.errors.length) {
throw new Error(result.errors.map((e) => e.message).join("; "));
}
return result;
});
}
// Maps the legacy "Role" string (as used by the old permissionType REST parameter)
// to the SGS GraphQL UserPermissionType enum.
function toPermissionEnum(role){
var roleMap = {
"viewer": "VIEWER",
"publisher": "PUBLISHER",
"siteadmin": "SITE_ADMIN",
"site admin": "SITE_ADMIN",
"superadmin": "SUPER_ADMIN",
"super admin": "SUPER_ADMIN"
};
var mapped = role ? roleMap[role.trim().toLowerCase()] : undefined;
if (!mapped) throw new Error("Unknown role \"" + role + "\". Valid roles: Viewer, Publisher, SiteAdmin, SuperAdmin.");
return mapped;
}
// CreateUser requires a groupId. Resolve it by name (if groupName is set) or fall
// back to the site's first group.
function getGroupId(groupName){
var query = `
query GetGroups($search: String) {
Groups(searchString: $search, maxRecords: 50) {
groupDtoList { id name }
}
}`;
return graphQL(query, { search: groupName || null }).then((result) => {
var groups = result.data.Groups.groupDtoList;
if (!groups.length) throw new Error("No groups were found on this site.");
if (groupName) {
var exact = groups.find((g) => g.name.toLowerCase() === groupName.toLowerCase());
if (exact) return exact.id;
throw new Error("Group \"" + groupName + "\" was not found.");
}
return groups[0].id;
});
}
function checkUser(userName){
//check if user exist and implement policy: addUser > login OR login OR deny access
// in this example, the code checks if the user exist: exist? log them in OR does not exist? create one and then log them in.
var query = `
query GetUser($search: String) {
Users(searchString: $search, maxRecords: 50) {
totalUsers
errorMessages
userDtoList { username }
}
}`;
graphQL(query, { search: userName }).then((result) => {
var users = result.data.Users.userDtoList;
var exists = users.some((u) => u.username.toLowerCase() === userName.toLowerCase());
if (!exists) { addUser(userName, newUserPassword, siteName); } else { login(param); }
}).catch((error) => { alert("Could not check user: " + error.message); });
}
function addUser(userName, newUserPassword, siteName){
// in case policy wants to add user, use the CreateUser mutation to add them.
getGroupId(groupName).then((groupId) => {
var mutation = `
mutation CreateUser($input: UserInput!) {
CreateUser(userInputDto: $input) {
actionResultDtoList { successStatus errorMessage key errorCode }
userDtoList { username }
}
}`;
var variables = {
input: {
username: userName,
password: newUserPassword,
permission: toPermissionEnum(Role),
groupId: groupId,
isActive: true,
passwordChangeRequired: false,
limitSessionsFlag: false,
limitStorageFlag: false,
limitSessionsValue: 0,
limitStorageValueMB: 0,
allowNotifications: true,
isSGCloudUser: false
}
};
return graphQL(mutation, variables);
}).then((result) => {
var actionResults = result.data.CreateUser.actionResultDtoList;
var failed = actionResults.find((r) => !r.successStatus);
if (!failed) { login(param); } else { alert("could not add user: " + (failed.errorMessage || failed.key)); }
}).catch((error) => {
alert("could not add user: " + error.message);
});
}
function login(param){
//redirect to origin (TEF\SG\TED) with the known credentials. The location is set automatically. So, if the user attempted to log in from TEF, they will be redirected to the same application.
window.location = server + "oauth/redirect?" + param;
}
</script>
</body>
</html>