CoSigning Web Applications

Web Applications can be integrated with CoSign with minimal development effort. The general idea is that CoSign sets some environmental variables, such as $REMOTE_USER, that the web app can use. In a single-sign-on system like CoSign, the meaning of "logout" can get confusing.

 

General Information

The CoSign filter functions similarly to the mod_authmodule for Apache in that requests are intercepted and verified before reaching the web application protected by CoSign.

When a user makes a request to a CoSign-protected web application, the CoSign filter checks that appropriate HTTP headers containing cryptographic information have been sent by the user. If that information is missing, the filter redirects the user to the global login page at https://weblogin.pennkey.upenn.edu/login. After successful authentication the user is redirected back to the original web application, passing along the expected HTTP headers and cryptographic information which is used by the CoSign filter to verify that the user has authenticated successfully.

From the CoSign-protected web application's perspective, a user should never arrive without having a number of CGI environment variables accompanying their request. These variables contain information on the authentication method used, the username, the CoSign service to which access has been granted, and the authentication factors met by the user.

These variables and their contents are listed below:

Variable Use
Auth_Type Authentication method used
COSIGN_FACTOR="UPENN.EDU" Space-separated list of the authentication factors submitted by user
COSIGN_SERVICE="cosign-domain-app-0" Name of CoSign service as understood by filter
REMOTE_USER="darianp" PennKey (username) of the user

A cookie named for the value of COSIGN_SERVICE is also set, containing cryptographic information used by the CoSign filter to verify the user's permission to access the service noted in COSIGN_SERVICE.

ISC recommends that CoSign-protected web applications verify the existence of the previous environment variables and cookie in addition to adhering to the following best practices.

Best Practices

General

  • Use SSL for access to application

SSL is not explicitly required in order to use CoSign, however ISC is requiring the use of SSL by any application that uses the University of Pennsylvania CoSign deployment for authentication.

Many of our users use public networks lacking encryption; using SSL at the application layer helps to insure integrity on a per-application basis, decreases the risk of cookie theft and information leakage, and increases the general defensive posture of the application.

Verify Authentication

  • Implement an authentication abstraction layer supporting HTTP Basic/Digest-style authentication, as well as any pre-existing application-specific form-based authentication

The CoSign filter presents itself to web applications similarly to HTTP Basic/Digest-style authentication. Applications which tomare written to take advantage of this form of authentication (ie., consuming the REMOTE_USER environment variable) may be "CoSigned" with minimal changes.

Applications that use form-based authentication will need to be modified to skip presentation of any authentication form when the aforementioned environment has been set by the CoSign filter. Additionally, such applications also need to be modified to use the REMOTE_USER environment variable as the username/login of the authenticate user, rather than input submitted from custom authentication forms.

Handling Logout

  • Specify application logout URL as CosignAllowPublicAccess (Apache), AllowPublicAccess (IIS), or Unprotected (IIS)

 

Application-Server-Specific Information:

Tomcat

If you are using Apache httpd server to proxy connections to a Tomcat Application server make sure the following is in place:

  • The Tomcat application server is secured so that only the Apache httpd service can access it via AJP/HTTP
  • Make sure the AJP Connector attribute request.tomcatAuthentication="false":
    <Connector port="8009" address="127.0.0.1" request.tomcatAuthentication="false" protocol="AJP/1.3" ... 
  • If you are using the Coyote JK connector make sure you set request.tomcatAuthentication=false in the workers.properties file

This allows the REMOTE_USER and AUTH_TYPE environment variables to pass through into the Tomcat request environment.  

 

See Also

 

Platform-Specific Information

Verifying Authentication

<%@ Page Language="C#" %>
<script runat="server">
void Page_Load(Object sender, EventArgs args) {

if ( !isNullOrEmpty(Request.ServerVariables["REMOTE_USER"]) ) {
/*
* REMOTE_USER received, however auth could have been
* via HTTP Basic or Digest; we check further below
*/
if ( !isNullOrEmpty(Request.Headers["COSIGN_SERVICE"]) ) {
if ( !isNullOrEmpty(Request.Cookies[ Request.Headers["COSIGN_SERVICE"] ].Value) ) {
/*
* Authentication was successful; do something like
* redirect the user to your application homepage
* and allow them to continue on about their business
*/
Response.Write("CoSign authentication verified.");
return;
}
}
}

/*
* This condition is only reached if CoSign is misconfigured; be sure
* to fail securely, disallowing access until the problem is resolved
*/
Response.Write("CoSign is misconfigured.");

}

bool isNullOrEmpty ( string str ) {
return (str == null || str.Length == 0);
}
</script>

 Warning: Whenever a header is being used it should be considered un-trusted data as headers may be coming from a user session.

Logging Out

<%@ Page Language="C#" %>
<script runat="server">
void Page_Load(Object sender, EventArgs args) {

if ( !isNullOrEmpty(Request.Headers["COSIGN_SERVICE"]) ) {
if ( !isNullOrEmpty(Request.Cookies[ Request.Headers["COSIGN_SERVICE"] ].Value) ) {
HttpCookie service_cookie = Request.Cookies[ Request.Headers["COSIGN_SERVICE"] ];

// Set expiration to a value in the past (one day ago)
service_cookie.Expires = DateTime.Now.AddDays(-1);

// Overwrite existing cookie value
service_cookie.Value = "deleted";

/*
* Leave all other cookie values intact
* and append modified cookie to response
*/
Response.Cookies.Add(service_cookie);

// Here you should also delete session cookie(s) set by your application

// Send expired cookie and redirect to global logout
Response.Redirect("https://weblogin.pennkey.upenn.edu/logout", true);
}
}

}

bool isNullOrEmpty ( string str ) {
return (str == null || str.Length == 0);
}
</script>

Verifying Authentication

#!/usr/bin/perl

use strict;
use warnings;

use CGI;

my $q = new CGI;

if ( $q->remote_user() )
{
# REMOTE_USER received, however auth could have been
# via HTTP Basic or Digest; we check further below
if ( $ENV{'AUTH_TYPE'} eq 'Cosign' && exists $ENV{'COSIGN_SERVICE'} )
{

if ( $q->cookie( $ENV{'COSIGN_SERVICE'} ) )
{
# Authentication was successful; do something like
# redirect the user to your application homepage
# and allow them to continue on about their business
}
}
}

# Something failed above; redirect user to login page
print $q->redirect('https://weblogin.pennkey.upenn.edu/login');

Logging Out

#!/usr/bin/perl

use strict;
use warnings;

use CGI;
use CGI::Cookie;

my $q = new CGI;

# Expire CoSign service cookie
my $service_cookie = new CGI::Cookie(
-name => $ENV{ COSIGN_SERVICE },
-value => 'deleted',
-expires => '-1d', # A value in the past (one day ago)
-domain => 'example.upenn.edu',
-path => '/', # Path is always '/'
-secure => 1 # 0 if CosignHttpOnly is set 'On';
# 1 if 'Off' or not set
);

# Here you should also delete session cookie(s) set by your application

# Send expired cookie and redirect to global logout
print $q->header(
-cookie => [$service_cookie], # Add additional cookie objects to this array
-location => 'https://weblogin.pennkey.upenn.edu/logout',
-status => '302',
);

Please note that environment variables differ between IIS and Apache as noted below:

APACHE IIS6/7
AUTH_TYPE (Unavailable)
COSIGN_FACTOR HTTP_COSIGN_FACTOR
COSIGN_SERVICE HTTP_COSIGN_SERVICE
REMOTE_USER HTTP_REMOTE_USER

Verifying Authentication

<?php

if ( isset( $_SERVER['REMOTE_USER'] ) && !empty( $_SERVER['REMOTE_USER'] ) )
{
/*
* REMOTE_USER received, however auth could have been
* via HTTP Basic or Digest; we check further below
*/
if ( $_SERVER['AUTH_TYPE'] == 'Cosign' && isset( $_SERVER['COSIGN_SERVICE'] ) )
{
/*
* PHP replaces '.' with '_' in $_COOKIE array keys, so we
* do the same in order to index the CoSign service cookie
*/
$service_name = preg_replace('/\./', '_', $_SERVER['COSIGN_SERVICE']);

if ( isset( $_COOKIE[$service_name] ) )
{
/*
* Authentication was successful; do something like
* redirect the user to your application homepage
* and allow them to continue on about their business
*/
echo $_SERVER['REMOTE_USER'], ' is logged in.';
exit;
}
}
}

/*
* Something failed above; redirect user to login page
*/
header('Location: https://weblogin.pennkey.upenn.edu/login');

?>

 

Logging Out

<?php

/*
* PHP replaces '.' with '_' in $_COOKIE array keys, so we
* do the same in order to index the CoSign service cookie
*/
$service_name = preg_replace('/\./', '_', $_SERVER['COSIGN_SERVICE']);

/*
* Expire CoSign service cookie
*/
setcookie(
$service_name, // Name
null, // Value
time()-(60*60*24), // Expiration: a value in the past (1 day ago)
'/', // Path: always '/'
null, // Domain
true // Secure: false if CosignHttpOnly is set 'On';
// true if 'Off' or not set
);

/*
* Here you should also delete session cookie(s) set by your application
*/

/*
* Redirect to global logout page
*/
header('Location: https://weblogin.pennkey.upenn.edu/logout');

?>

 

Assumes you have a few application variables.

application.url: the full url to your site.
<!--- Cosign Struct --->
<cfset application.cosignStruct = StructNew() />
<!--- Set the values to match the config file values in your Service--->
<!--- the Service RequireFactor --->
<cfset application.cosignStruct.COSIGN_FACTOR = "UPENN.EDU" />
<!--- the Service Name --->
<cfset application.cosignStruct.COSIGN_SERVICE = "YOUR_SERVICE_NAME" />
<!--- the Service RequireFactor --->
<cfset application.cosignStruct.REMOTE_REALM = "UPENN.EDU" />

 

Verifying Authentication

<!--- get the header value struct which should contain COSIGN_FACTOR, COSIGN_SERVICE, REMOTE_REALM, and REMOTE_USER --->
<cfset headerStruct = GetHttpRequestData().headers />
<!--- make sure that the headerStruct has all the keys, they all have a value,
and that all but the remote user match the cosignStruct --->
<cfif structkeyexists(headerStruct,"COSIGN_FACTOR") and
structkeyexists(headerStruct,"COSIGN_SERVICE") and
structkeyexists(headerStruct,"REMOTE_REALM") and
structkeyexists(headerStruct,"REMOTE_USER") and
len(headerStruct.COSIGN_FACTOR) and
len(headerStruct.COSIGN_SERVICE) and
len(headerStruct.REMOTE_REALM) and
len(headerStruct.REMOTE_USER) and
comparenocase(application.cosignStruct.COSIGN_FACTOR, headerStruct.COSIGN_FACTOR) eq 0 and
comparenocase(application.cosignStruct.COSIGN_SERVICE, headerStruct.COSIGN_SERVICE) eq 0 and
comparenocase(application.cosignStruct.REMOTE_REALM, headerStruct.REMOTE_REALM) eq 0
>
<!--- check if a cookie exists that matches the cosignStruct.cosign_service,
and if so perform the login using the REMOTE_USER value --->
<cfif structkeyexists(cookie,headerStruct.COSIGN_SERVICE) and len(cookie['#headerStruct.COSIGN_SERVICE#'])>
<!--- ToDo: Authentication Successful, Perform addition Authorization if necessary,
and log user into application --->

</cfif>
<cfelse>
<!--- ToDo: Authentication Failed,
Do something if the cookie doesn't validate --->
</cfif>

Logging Out

<cfheader name="Set-Cookie" value="#application.cosignStruct.COSIGN_SERVICE#=;path=/;expires=#now()#;" />
<!--- ToDo: Perform other actions to logout of your application --->
<cflocation url="https://weblogin.pennkey.upenn.edu/logout?#application.url#" />

MediaWiki

MediaWiki can be converted to use CoSign like this:

  1. Install the HttpAuth extension for MediaWiki.
  2. Add the following to the end of the LocalSettings.php file:

 /* Added for Cosign Authentication */ session_start(); if ( (! empty($_SERVER['REMOTE_USER'])) || $_COOKIE['fpwiki_en_UserID']) { // echo "got here " . $_SERVER['REMOTE_USER']; $_SERVER['PHP_AUTH_USER']=$_SERVER['REMOTE_USER']; $_SERVER['PHP_AUTH_PW']='fakepassword'; require_once("$IP/extensions/HttpAuthPlugin.php"); $wgAuth = new HttpAuthPlugin(); $wgHooks['UserLoadFromSession'][] = array($wgAuth,'autoAuthenticate'); }

phpBB

The web forum application phpBB can be converted to use CoSign for authentication.

Notes

  • The following modification is UPenn specific. Cosign authenticated users are automatically registered with pennkey@upenn.edu as their email address.

Instructions

  1. Create a user in phpBB with the same username as your pennkey and give this user complete admin privileges.
  2. Cosign protect the directory where the phpbb files are.
  3. Create a new file in the phpBB directory includes/auth/auth_cosign.php with the following text in it:

<?php
/**
* Cosign auth plug-in for phpBB3
*
* Authentication plug-ins is largely down to Sergey Kanareykin, our thanks to him.
*
* @package login
* @version $Id: auth_apache.php 8602 2008-06-04 16:05:27Z naderman $
* @copyright (c) 2005 phpBB Group
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
*
* converted auth_apache to auth_cosign - austin murphy 5/29/09
*/

/**
* @ignore
*/
if (!defined('IN_PHPBB'))
{
exit;
}

$lang['LOGIN_ERROR_EXTERNAL_AUTH_COSIGN'] = 'You have not been authenticated by Cosign';
$lang['COSIGN_SETUP_BEFORE_USE'] = 'You have to setup CoSign authentication before you switch phpBB to this authentication method. Keep in mind that the username you use for CoSign authentication has to be the same as your phpBB username.';

// The above language defs would normally be set in the following files:
// language/en_us/common.php
// language/en_us/acp/board.php

/**
* Checks whether the user is identified to cosign
* Only allow changing authentication to cosign if the user is identified
* Called in acp_board while setting authentication plugins
*
* @return boolean|string false if the user is identified and else an error message
*/
function init_cosign()
{
global $user;

if (!isset($_SERVER['AUTH_TYPE']) || $_SERVER['AUTH_TYPE'] !== 'Cosign' || !isset($_SERVER['REMOTE_USER']) || $user->data['username'] !== $_SERVER['REMOTE_USER'])
{
return $user->lang['COSIGN_SETUP_BEFORE_USE'];
}
return false;
}

/**
* Login function
*/
function login_cosign(&$username, &$password)
{
global $db;

if (!isset($_SERVER['REMOTE_USER']))
{
return array(
'status' => LOGIN_ERROR_EXTERNAL_AUTH,
'error_msg' => 'LOGIN_ERROR_EXTERNAL_AUTH_COSIGN',
'user_row' => array('user_id' => ANONYMOUS),
);
}

$php_auth_user = $_SERVER['REMOTE_USER'];
$php_auth_pw = 'nopassword';

if (!empty($php_auth_user) && !empty($php_auth_pw))
{
if ($php_auth_user !== $username)
{
return array(
'status' => LOGIN_ERROR_USERNAME,
'error_msg' => 'LOGIN_ERROR_USERNAME',
'user_row' => array('user_id' => ANONYMOUS),
);
}

$sql = 'SELECT user_id, username, user_password, user_passchg, user_email, user_type
FROM ' . USERS_TABLE . "
WHERE username = '" . $db->sql_escape($php_auth_user) . "'";
$result = $db->sql_query($sql);
$row = $db->sql_fetchrow($result);
$db->sql_freeresult($result);

if ($row)
{
// User inactive...
if ($row['user_type'] == USER_INACTIVE || $row['user_type'] == USER_IGNORE)
{
return array(
'status' => LOGIN_ERROR_ACTIVE,
'error_msg' => 'ACTIVE_ERROR',
'user_row' => $row,
);
}

// Successful login...
return array(
'status' => LOGIN_SUCCESS,
'error_msg' => false,
'user_row' => $row,
);
}

// this is the user's first login so create an empty profile
return array(
'status' => LOGIN_SUCCESS_CREATE_PROFILE,
'error_msg' => false,
'user_row' => user_row_cosign($php_auth_user),
);
}

// Not logged into cosign
return array(
'status' => LOGIN_ERROR_EXTERNAL_AUTH,
'error_msg' => 'LOGIN_ERROR_EXTERNAL_AUTH_COSIGN',
'user_row' => array('user_id' => ANONYMOUS),
);
}

/**
* Autologin function
*
* @return array containing the user row or empty if no auto login should take place
*/
function autologin_cosign()
{
global $db;

if (!isset($_SERVER['REMOTE_USER']))
{
return array();
}

$php_auth_user = $_SERVER['REMOTE_USER'];
// $php_auth_pw = $_SERVER['PHP_AUTH_PW'];
$php_auth_pw = 'nopassword';

if (!empty($php_auth_user) && !empty($php_auth_pw))
{
set_var($php_auth_user, $php_auth_user, 'string', true);
set_var($php_auth_pw, $php_auth_pw, 'string', true);

$sql = 'SELECT *
FROM ' . USERS_TABLE . "
WHERE username = '" . $db->sql_escape($php_auth_user) . "'";
$result = $db->sql_query($sql);
$row = $db->sql_fetchrow($result);
$db->sql_freeresult($result);

if ($row)
{
return ($row['user_type'] == USER_INACTIVE || $row['user_type'] == USER_IGNORE) ? array() : $row;
}

if (!function_exists('user_add'))
{
global $phpbb_root_path, $phpEx;

include($phpbb_root_path . 'includes/functions_user.' . $phpEx);
}

// create the user if he does not exist yet
user_add(user_row_cosign($php_auth_user));

$sql = 'SELECT *
FROM ' . USERS_TABLE . "
WHERE username_clean = '" . $db->sql_escape(utf8_clean_string($php_auth_user)) . "'";
$result = $db->sql_query($sql);
$row = $db->sql_fetchrow($result);
$db->sql_freeresult($result);

if ($row)
{
return $row;
}
}

return array();
}

/**
* This function generates an array which can be passed to the user_add function in order to create a user
*/
function user_row_cosign($username)
{
global $db, $config, $user;
// first retrieve default group id
$sql = 'SELECT group_id
FROM ' . GROUPS_TABLE . "
WHERE group_name = '" . $db->sql_escape('REGISTERED') . "'
AND group_type = " . GROUP_SPECIAL;
$result = $db->sql_query($sql);
$row = $db->sql_fetchrow($result);
$db->sql_freeresult($result);

if (!$row)
{
trigger_error('NO_GROUP');
}

// generate user account data
$password = 'nopassword';
return array(
'username' => $username,
'user_password' => phpbb_hash($password),
'user_email' => $_SERVER['REMOTE_USER'] . '@upenn.edu',
'group_id' => (int) $row['group_id'],
'user_type' => USER_NORMAL,
'user_ip' => $user->ip,
);
}

/**
* The session validation function checks whether the user is still logged in
*
* @return boolean true if the given user is authenticated or false if the session should be closed
*/
function validate_session_cosign(&$user)
{
if (!isset($_SERVER['REMOTE_USER']))
{
return false;
}

$php_auth_user = '';
set_var($php_auth_user, $_SERVER['REMOTE_USER'], 'string', true);

return ($php_auth_user === $user['username']) ? true : false;
}

?>

  1. Navigate to the phpBB install and verify that you are logged into CoSign.
  2. Within the phpBB Admin Control Panel, select CoSign as the authentication type.

RT

 Request Tracker can be set to honor $REMOTE_USER by

  1. Cosign protect the entire RT installation.
  2. Edit rt3/etc/RT_SiteConfig.pm to include this:

# Use Remote User
Set($WebExternalAuth , "1");

Drupal - Logging Out

<?php
// Prevent users from "backing up" to the site after log-out
global $user;
watchdog('user', 'Session closed for %name.', array('%name' => $user->name));

// Unset cookies
foreach($_COOKIE AS $key => $value)
{
setcookie($key,$value,1);
unset($_COOKIE[$key]);
}

// Destroy the current session
session_destroy();
$_SESSION = array();

// Only variables can be passed by reference workaround
$null = NULL;
user_module_invoke('logout', $null, $user);

// Load the anonymous user
$user = drupal_anonymous_user();

header('Cache-Control: no-cache');
header('Expires: -1');

// Send page to logout cgi
$query_string = $_SERVER['QUERY_STRING'];
$service_name = $_SERVER['COSIGN_SERVICE'];
$central = "https://weblogin.pennkey.upenn.edu/logout/logout.cgi";
setcookie($service_name, "null", time()-3600, '/', "", 1 );
header( "Location: $central?$query_string" );
?>

Print This Page Share:
Date Posted: April 16, 2013 Tags: Provider Resource, Web, CoSign, Server

Was this information helpful?

Login with PennKey to view and post comments