Config 4.1: Protect Static Content

In pattern, set Case to be Static,  and assign a protected role in the pattern match or in Que.

For this case, the matched content is a permanent static file such as an image, a video or a PDF. By inspecting if there is a protected role from the match, you can enforce visitors to login before viewing it. For example, visiting to

http://WEBSITE/admin/1234.html

will be matched to the pre-defined pattern

{
"Reg": "^\/(\w+)/(\d+)\.(\w+)\/$"
"Mats": ["role", "book_id", "tag"]
"Case": "Static"
}

From the matched list of variables, we have role=admin, book_id=1234 and tag=html. Since admin is a protected role, one has to login in order to view the file.

The Static case will always look for a static file on disk, whether or not it actually exists. If not, a 404 Not Found error will be displayed.

Config 4: URL Handling

  1. General Properties
  2. Mime Types
  3. Authentication & Authorization
  4. URL Handling
    1. Protect Static Content
    2. Rerouting
    3. Caching
  5. Tasks

 

Although your URLs have a strict format, you can custom them using the URL routing feature in the configuration,  defined as Patterns in config.json. Not only rerouting, but it also provides a way to protect static contents for roles, and to cache dynamic contents onto disk.

"Patterns": [pattern, pattern, ...]

where object pattern is defined to be a map with the keys:

  • Case:  Static, Cache or Reroute. Default to Reroute.
  • Reg: a Regular Expression string. The first matched URL pattern will terminate the iteration.
  • Mats: the matched values will be assigned to the variables in this array.
  • Que: a query string to add extra variables by hand.
  • Expire:  in case of Cache, how many seconds the page should be expired.

If you use the built-in httpd server as in GO and Java, serving static content or rerouting URL is straightforward to do. In case of 3rd-party Apache server, you can use Fast CGI’s AAA handling feature. But that discussion goes a bit off-topics, which we’d like to make a separate document.

 

Config 3.6: Logout

The general URL format is:

http://WEBSITE/HANDLER/role/mime/component

If component matches the pre-defined Logout_name in config.json, the request, usually a GET method, will be treated as logout from role.

If you are working on JSON, the logout will return you {“data”:”logout”} . If on HTML, it will logout and then redirect you to Logout defined in the gate.

Internally, the logout action is to delete the role cookie in browser.

Config 3.5: Oauth

If the component matches one of the following Social Network sites, also defined in config.json, we will interpret the request as an Oauth request.

Google_provider,   default google
Facebook_provider, default facebook
Twitter_provider,  default twitter
Linkedin_provider, default linkedin

Here is the list of steps you should be proceed to set up an OAuth issuer.

Step 1, Provider’s Parameters

You need to setup the Oauth service at provider’s side. Please consult their developments for details. After it is done, you should receive a few relevant parameters. Put them in Provider_pars of the issuer.

Step 2, Build Issuer

For OAuth1 you should always use “Credential” : [“oauth_token”, “oauth_verifier”] , and for OAuth2, “Credential”: [“code”, “error”].

So the issuers in config.json will look like:

"Issuers": {
  "facebook": {
    "Provider_pars": {
      "client_id": "1111111111",
      "client_secret": "xxxxxxxx"
    },
    "Credential": ["code", "error"],
    "In_pars": ["id", "first_name", "last_name", "client_id", "access_token", "expires"],
    "Out_pars": array,
    "Sql": string
  },
  "google": {
    "Provider_pars": {
      "client_id": "111111-xxxxxxxx.apps.googleusercontent.com",
      "client_secret": "xxxxxxxxx"
    },
    "Credential" : ["code", "error"],
    "In_pars": ["id", "given_name", "family_name", "client_id", "access_token", "expires_in"],
    "Out_pars": array,
    "Sql": string
  },
  "twitter": {
    "Provider_pars": {
      "oauth_consumer_key": "xxxxxxxxxxx",
      "oauth_consumer_secret": "xxxxxxxx",
      "owner_id" : "11111111"
    },
    "Credential" : ["oauth_token", "oauth_verifier"],
    "In_pars" : ["user_id", "screen_name", "oauth_token", "oauth_token_secret", "x_auth_expires"],
    "Out_pars": array,
    "Sql": string
  },
  "linkedin": {
    "Provider_pars" : {
      "oauth_consumer_key": "xxxxxxx',
      "oauth_consumer_secret":  "xxxxxxxxxxx',
      "oauth_endpoint": "http://api.linkedin.com/v1/people/~:(id,first-name,last-name)"
    },
    "Credential" : ["oauth_token", "oauth_verifier"],
    "in_pars": ["id", "firstName", "lastName", "oauth_token", "oauth_token_secret", "oauth_authorization_expires_in", "oauth_expires_in"],
    "Out_pars": array,
    "Sql": string
  }
}

In the above setup, you are assumed to collect the minimal sets of user information so as to keep the best privacy. To collect more user information, you can specify them in scope or end_point.

Step 3: Create Sql

When a visitor hits an OAuth URL, she will be redirected to the provider’s site. After her successful login and authorization, she will eventually land on your website again with variables defined in In_pars.

At this moment, Genelet will run the stored procedure Sql , to query data, to execute transactions and finally to generate a set of output variables defined in Out_pars. What exactly Sql should be depends on your data model in the project.

Attributes, which defines variables stored in the login cookie, should be a subset of Out_pars. If the later is not explicitly defined, then is default to Attributes.

Step 4: Debugging and Errors

Since you are not able to code any program for the relatively sophisticated OAuth process, please take a look at the debugging messages. If everything goes correctly, Sql will run which is under your control. Like the db issuer,  you should return an empty value for the first element in Attributes in case you want to deny this OAuth attempt.

Step 5: Issuing Ticket

After the Attribute variables are generated, the same step as that in the db issuer will be proceeded. Specifically, a login cookie will be saved into the browser and used for followup visits at the gate until the expiration or invalidation.

Config 3.4: Issuer db

 

Simple SQL

A popular authentication is to use an user-account table to logins. For example,  assuming the table polls_admin and the credential columns are admin_name and admin_pass, you can setup the following issuer:

"Issuers : {
  "db" : {
    "Default": true,
    "Credential" : ["login", "passwd"],
    "Sql": "select admin_name, admin_id FROM polls_admin WHERE admin_name=? and admin_pass=?", 
  }
}

where the first two variables login and passwd, passed in the login form, will be used in the Sql for search. A zero return will mean a failed login.

 

Issuer db with Stored Procedure

In a real project, the above database issuer should setup a few security rules to stop spams. For example, you may want to detect how many failed attempts for a login name or from an IP during the past hour or past day. If they exceed certain thresholds, you may block the account or IP for 24 hours.

In all those sophisticated cases, Genelet asks you to build one stored procedure for authentication. For the db issuer, following these steps to build an login procedure:

  1. The first two inputs to the procedure are always the credential pair defined in Credential.
  2. There are 4 more optional input variables depending on Screen. If it’s Logic-And 1, then the URL will be added to inputs; 2 for IP (as a 32-bit unsigned integer), 3 for User Agent and 4 for Referer.
  3. Your outputs are values of variables defined in Attributes of the gate.
  4. The first item in outputs should always be the value of Id_name.
  5. For failed login, just return an empty value for Id_name.

Here is an example to use db issuer of MySQL to issue a member ticket.

"Roles" : {
  "m": {
    "Attributes": ["member_id", "email", "first_name", "last_name", "status_id"],
    "Type_id": 1,
    "Id_name": "member_id",
    "Is_admin": false,
    "Surface": "mc",
    "Domain": ".example.com",
    "Duration": 360000,
    "Max_age": 360000,
    "Secret": "11223344556677889900aabbccddeeffgg",
    "Coding": "11223344556677889900mmnnbbvvccxxzz",
    "Logout': "/",
    "Issuers": {
      "db" : {
        "Default": true,
        "Screen": 2,
        "Sql": "proc_member",
        "Credential": ["email", "passwd"]
      }
    }
  }
}

Here is the member table, the login history table and the stored procedure proc_member that authenticates login and blocks rough attempts. If an email or IP had 5 failed attempts during the past hour or 20 during the past 24 hours, it will be blocked for 24 hours.

DROP TABLE IF EXISTS `cus_member`;
CREATE TABLE `cus_member` (
  `member_id` int(10) unsigned NOT NULL,
  `email` varchar(48) NOT NULL,
  `passwd` char(40) NOT NULL,
  `first_name` varchar(64) NOT NULL,
  `last_name` varchar(32) NOT NULL,
  `state_id` tinyint unsigned DEFAULT NULL,
  PRIMARY KEY (`member_id`),
  UNIQUE KEY `email` (`email`),
  KEY `status_id` (`status_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `cus_ip`;
CREATE TABLE `cus_ip` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `ip` int(10) unsigned NOT NULL,
  `email` varchar(48) NOT NULL,
  `updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `ret` enum('fail','success') NOT NULL DEFAULT 'fail',
  PRIMARY KEY (`id`),
  KEY `updated` (`updated`),
  KEY `ip` (`ip`,`email`(16))
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8;

DELIMITER ~
DROP PROCEDURE IF EXISTS proc_member;
CREATE PROCEDURE proc_member(IN i_email VARCHAR(48), IN i_passwd VARCHAR(24), IN i_ip INT unsigned, OUT o_member_id INT unsigned, OUT o_email VARCHAR(48), OUT o_first_name VARCHAR(64), OUT o_last_name VARCHAR(32), OUT o_status_id tinyint unsigned)
BEGIN
DECLARE c1 INT;
DECLARE c2 INT;
SELECT COUNT(*) INTO c1 FROM cus_ip WHERE ret='fail' AND ip=i_ip AND email=i_email AND (UNIX_TIMESTAMP(updated) >= (UNIX_TIMESTAMP(NOW())-3600));
SELECT COUNT(*) INTO c2 FROM cus_ip WHERE ret='fail' AND ip=i_ip AND (UNIX_TIMESTAMP(updated) >= (UNIX_TIMESTAMP(NOW())-24*3600));
IF (c1<=5 AND c2<=20) THEN
  SELECT member_id, email, first_name, last_name, status_id
  INTO o_member_id, o_email, o_first_name, o_last_name, o_status_id
  FROM cus_member
  WHERE email=i_email and passwd=SHA1(i_passwd) and status_id IN (1);

  IF ISNULL(o_member_id) THEN
    INSERT INTO cus_ip (ip, email, ret) VALUES (i_ip, i_email, 'fail');
  ELSE
    DELETE FROM cus_ip WHERE ret='fail' AND ip=i_ip AND (UNIX_TIMESTAMP(updated) >= (UNIX_TIMESTAMP(NOW())-24*3600));
    INSERT INTO cus_ip (ip, email, ret) VALUES (i_ip, i_email, 'success');
  END IF;
ELSE
  SELECT '1030' INTO o_email;
END IF;
END~
DELIMITER ;

Config 3.3: Issuer plain

If you run the help command, a special issuer, plain, will be assigned to the admin gate, using login name hello and password world.

"Issuers" : {
  "plain" : {
    "Default" : true,
    "Credential" : ["login", "passwd"],
    "Provider_pars": {"Def_login":"hello", "Def_password":"world"}
  }
}

The default login and password strings are defined in Def_login and Def_password in Provider_pars. You may change them.

Config 3.2: Login and Issuers

To enter a gate, you need to buy a ticket or login. You can buy the ticket from multiple issuers so Issuers is define to be a multiple-entry map:

Issuers: {
  provider: issuer object,
  provider: issuer object, ...
}

The issuer object consists of the following key-value pairs:

  • Default , true means it’s the default issuer of this gate.
  • Sql, string. Usually a stored procedure to authenticate the login.
  • Credential, array of credential variables in the login form. In case of Oauth, this may consists of provider’s error codes.
  • In_pars, for Oauth only, array of input parameters to Sql.
  • Out_pars, for Oauth only, array of output parameters from Sql. Default to be the same as Attributes.
  • Sql_as, string. An admin role can login as a normal role with this stored procedure.
  • Screen, integer, Logic And 1 for attaching URL, 2 for IP, 4 for user agent, 8 for referrer.
  • Provider_pars, an object to pass provider’s parameters to the issuer;

 

To Login

Note that the general URL format is:

http://WEBSITE/HANDLER/role/mime/component

If component matches the pre-defined Login_name in config.json, the request will be treated as a login to role. You need to post a web form containing the first two variables in Credential. Optionally, you may attach provider so Genelet knows which issuer to use, and go_uri so Genelet knows where to redirect after login. See development manuals for details.

Config 3.1: Roles and Gates

In config.json,  roles are defined by

"Roles": {
  role name: gate object,
  role name: gate object, ...
}

So each role is protected by a gate which is an object consisting of the following key-values:

  • Id_name, the unique ID name for this role;
  • Type_id, the role type id; optional
  • Is_admin (=true or false), if this is an admin role;
  • Length, visitor’s IP number can be packed into 8 hex characters. How many used for validation.
  • Surface, the cookie name to save this role in browser;
  • Domain, the domain name for this cookie;
  • Path, the URL path that the cookie is valid; optional
  • Duration, the active period for this cookie;
  • Max_age, the same as Duration but for browser that supports Max_age; optional
  • Secret, secret string to sign the cookie using SHA1;
  • Coding, secret string to encrypt the cookie;
  • Userlist, an array. If defined, only those specific user Ids can login. optional.
  • Logout, after logout, where to redirect the visitor to. Default: the home page /.
  • Attributes, an array of role’s parameters saved in the cookie;

When a visitor presents her ticket at the gate, it will use those information to validate it and de-crypt it into incoming X-Forwarded headers, which could be further used in other middleware.

Here is the list of headers:

  • X-Forwarded-Time: when this ticket was generated as Unix time stamp.
  • X-Forwarded-Duration: how long the ticket was assumed to be valid, in seconds.
  • X-Forwarded-Request_Time: the current time stamp.
  • X-Forwarded-User: the unique user ID, which is defined to be the first element in Attributes.
  • X-Forwarded-Group: the rest of elements in Attributes concated by separator |.
  • X-Forwarded-Hash: SHA1 signature from Attributes, timestamp, IP and secret. This was setup at the authentication and saved in the cookie.

Meanwhile, the elements in attributes will acquire values from the gate and be passed on as incoming variables. If visitor has already passed a variable of the same name in query, it would be overriden by the gate value, since the later can’t be forged. For example, if visitor claims that she has a different user ID in query, the gate will override it by the one in the cookie that was setup at the authentication time.

Config 3: Authentication and Authorization

  1. General Properties
  2. Mime Types
  3. Authentication & Authorization
    1. Roles and Gates
    2. Login and Issuers
    3. Issuer plain
    4. Issuer db
    5. OAuth
    6. Logout
  4. URL Handling 
  5. Tasks

 

Note that role appears in the Genelet URL:

http://WEBSITE/HANDLER/role/mime/component?action=string&query...

Except for the public role, all other roles are protected and thus need authentications. As soon as a user signs in as the role successfully, a self-certified cookie, call ticket, will be saved in her user agent like browser. For follow-up visits, Genelet will act as a gate guard to inspect the ticket, to grant or to deny it based on its validation.

Genelet allows multiple authentication mechanisms (i.e. issuers) to issue the same ticket to a role. For example, for shopping-cart members, they can sign in via Facebook, via Google, or via local accounts which they registered early.

You can add unlimited security roles in Genelet, each working on a different URL group and being protected by own gate.

Authentication and authorization are managed solely by config.json. Web developers don’t need to do extra programming on it. Genelet supports database-based account, Oauth1 and Oauth2 logins, which should cover 95% of cases. We plan to support any major authentication method in the future.

 

 

 

Config 2.4: XML Types

To return XML types, you need to define few extra fields. E.g.

"x":{
    "Content_type":"application/xml; charset=UTF-8",
    "Short":"xml",
    "Challenge":"challenge",
    "Logged":"logged",
    "Logout":"logout",
    "Failed":"failed",
    "Case":2
  }

where Challenge, Logged, Logout and Failed are mandatory. Their meaning are:

  • Challenge: if visitor has no valid login ticket, instead of redirecting to a login screen, Genelet returns you:
    <?xml version=”1.0″ encoding=”UTF-8″?><data>challenge</data>
  • Logged: after a successful login, Genelet returns you:
    <?xml version=”1.0″ encoding=”UTF-8″?><data>logged</data>
  • Logout: after a successful logout, Genelet returns you:
    <?xml version=”1.0″ encoding=”UTF-8″?><data>logout</data>
  • Failed: if an error is found, Genelet returns you:
    <?xml version=”1.0″ encoding=”UTF-8″?><data>failed</data>

Finally, Case=2 is for XML.