CoSigningWebApplications
From Provider Wiki
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.
Contents |
General Information
The CoSign filter functions similarly to the mod_auth module 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="Cosign" | 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 are 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)
This provides for logical application behavior from the user's perspective in the instance where a user's application session expires while viewing a CoSign protected web page. In this case, with public access allowed for the application logout URL the user is able to click "Logout/Signoff" without being asked to reauthenticate.
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.
Platform-Specific Information
ASP.NET (C#)
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>
Perl
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',
);
PHP
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');
?>
ColdFusion
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#" />
More examples
MediaWiki
MediaWiki can be converted to use CoSign like this:
- Install the HttpAuth extension for MediaWiki.
- 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');
}
- Alternately, the extension Extension:AutomaticREMOTE_USER is reported to work better.
phpBB
See: CoSigningPhpBB
RT
Request Tracker can be set to honor $REMOTE_USER by
- Cosign protect the entire RT installation.
- 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" );
?>
See Also
