Into the Wild - Authenticate and Prevail on the Open Web
For anyone who wants to take a trip over to the east coast this weekend (4/14/2012), here's the perfect reason. BSides (#BsidesCHS) Charleston will offer several security related presentations. Tickets are available here.
These notes are for the security development section of the conference. This topic discusses distributed authentication and how to use the power of OAuth, SAML 2.0, and OpenId to simplify authentication and help bring in more end users into your site. There is so much to cover, so this topic is by no means comprehensive, but a good primer for these topics. I hope to see you there!
Title: Into the Wild - Authenticate and Prevail on the Open Web
Length: 50 mins
Abstract: Facebook, Twitter, Google Apps, SalesForce, Cisco, Intuit, and many apps have been using decentralized services to exchange authentication and authorization information across the web. Learn how to integrate authentication services into your application so that you can sign in once and navigate smoothly across other trusted domains. This presentation is hands-on and will include code (and lot's of it!) to communicate using SAML 2.0,
OpenID, OAuth, and more.
Goals:
- Talk about the idea of distributed authentication
- Define what’s out there in the world of federated authentication.
- Talk about when to use certain authentication methods.
- Drill into the details of some of the authentication methods and how each works.
- Break out the code OpenID , OAuth, SAML 2.0 (Apache, PHP, Ruby, Java/Grails, Spring)
Decentralized Authentication
A Few Qualities of A Decentralized Authentication System
1.) The application is stateless with regards to managing user identities. The application has no need for user names or passwords. Authentication is decoupled from the application code.
2.) A session stores enough information for the client to authenticate and authorize into an application. Allows single sign on across multiple applications.
3.) Open standards.
4.) Scalable
Terminology:
Service Providers - These are the services that are requesting that your ID is authenticated. This is usually coupled alongside the client application.
Identity Providers - The DMV of the distributed authentication world. This is where the identities are stored and depending on the method this is the source of trust for any requesting users attempting to authenticate.
Federated: When 2 or more identity providers have agreed to accept and/or provide themselves as authentication services.
Assertions - The license passed back from the Identity Providers back to the Service Providers who decide to accept or deny the authenticating user.
n-Legged - The number of parties involved during an OAuth authentication.
Relying party (RP) - The site that wants to verify the end-user's identifier.
Where Does this Idea of Decentralized Authentication Come From?
Decentralized authentication is not a new model. Think of your friendly neighborhood DMV!
- Your state DMV issued identification cards to drive a car. In order to receive a driver’s license you must present valid information that you are a qualified to drive and you are indeed the person that is being issued a driver’s license. (You request your credentials to be entered into the identity provider)
- Your are issued an identification card, and are qualified to drive in your state. (The identity provider has your credentials)
- You drive into the next state Georgia. Georgia has agreed with South Carolina to accept the premise that anyone issued a South Carolina driver’s license is qualified to drive in Georgia. Actually, South Carolina licenses are good in all 50 states. (The DMVs are federated!)
- You request the privilege of visiting Bob’s BierGarten across the state line. They check your ID and accept your are authentic based on your issued South Carolina driver’s license. (A service provider requests valid credentials in order to provide a service.)
- You enjoy a tasty beverage! (You enjoy your service provided!)
Whats Out There???
There are many available services, but for the focus of this discussion, we’ll focus on the following
OpenID -
openid.net - An open standard to allow users to authenticate in a decentralized manner.
OAuth -
oauth.net - Created from the need to allow dashboard widgets to authenticate with their services.
Implementation Details
OAuth
OAuth is a distributed authentication open standard that allows users to share username/password tokens versus credentials and has the following benefits
- Allows access delegation, similar to valet keys in luxury cars you can share only specific items. photos, files, etc
- Easy to use. You use your twitter/Netflix/Google account to authenticate
- Saves time and money. Sites can implement OAuth vs. building their own authentication system.
- Lowers the barrier of entry into a single website.
Added in many IdPs to reduce phishing: Pre-registration of the redirection URI
OpenID
OpenID is a distributed authentication open standard that allows users to share username/password tokens versus credentials and has the following benefits
- Allows access via an Identity Provider
- Easy to use. You use your twitter/Netflix/Google account to authenticate
- Saves time and money. Sites can implement OAuth vs. building their own authentication system.
- Lowers the barrier of entry into a single website.
Phishing - OpenID attempts to manage phishing by forcing the end user to be authenticated with the identity provider before the relying party authenticates. In general you will probably be able to validate the identity provider and verify the certificate via ssl and will be authenticating via the IdP vs. a relying party site.
SAML 2.0
SAML 2.0 has been originated and defined by the OASIS organization and is an XML-based protocol which provides secure tokens via assertions which contain attributes about the end user that is attempting authentication.
The following protocols are supported in SAML 2.0..
- Assertion Query and Request Protocol (What is the attribute of account?)
- Authentication Request Protocol (SP requests an authentication)
- Artifact Resolution Protocol (A reference to a SAML message is an artifact)
- Name Identifier Management Protocol (A protocol which allows change to the value or format of the name of a Principal)
- Single Logout Protocol (A URL that allows the user to invalidate their session)
- Name Identifier Mapping Protocol
Bindings Supported:
- SAML SOAP Binding (based on SOAP 1.1)
- Reverse SOAP (PAOS) Binding
- HTTP Redirect Binding
- HTTP POST Binding
- HTTP Artifact Binding
- SAML URI Binding
The following profiles are supported in SAML 2.0
- SSO Profiles
- Web Browser SSO Profile <<-- Typical use case (SP Post Request;IdP Post Response) && (SP Redirect Artifact; IdP Redirect Artifact)
- Enhanced Client or Proxy (ECP) Profile
- Identity Provider Discovery Profile
- Single Logout Profile
- Name Identifier Management Profile
- Artifact Resolution Profile
- Assertion Query/Request Profile
- Name Identifier Mapping Profile
- SAML Attribute Profiles (This is how attributes are queried)
- Basic Attribute Profile - name / value pairs
- X.500/LDAP Attribute Profile - Support for X.500 attributes
- UUID Attribute Profile - UUIDs can be attribute names
- DCE PAC Attribute Profile
- XACML Attribute Profile - Formatted to be processed by XACML (eXtensible Control Markup Language)
What is in an assertion?
- unique identifier of the IdP
- A digital signature of the <saml:Assertion> element
- An opaque transient identifier of the authenticated principal
- The conditions that an assertion will be valid
- Authentication information from the IdP
- An element containing information regarding the attributes released from the IdP after authentication.
Example Assertion:
<?xml version="1.0" encoding="UTF-8"?>
<saml2p:Response xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol"
Destination="https://musc.sample.com/saml/SSOAssert.aspx" ID="_3f8bc264hdfghfgjhba4548c8e468f54"
InResponseTo="_83ee9gsdfgdfgb-9cbf-42ffad75cb0b" IssueInstant="2012-04-06T18:13:53.300Z"
Version="2.0">
<saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion"
Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">https://musc.sample.com/shibboleth-sp</saml2:Issuer>
<saml2p:Status>
<saml2p:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" />
</saml2p:Status>
<saml2:Assertion xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion"
ID="_577663bbdsfgsdfg5e7017598c4d" IssueInstant="2012-04-06T18:13:53.300Z"
Version="2.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<saml2:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">https://musc.sample.com/shibboleth-sp
</saml2:Issuer>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
<ds:Reference URI="#_5776sdfgdsfgsf7598c4d">
<ds:Transforms>
<ds:Transform
Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
<ec:InclusiveNamespaces xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#"
PrefixList="xs" />
</ds:Transform>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<ds:DigestValue>XA0sdfgdsfg4UalAe8E=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>
</ds:SignatureValue>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>
<!-- Cert goes Here --> </ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
<saml2:Subject>
<saml2:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
NameQualifier="https://musc.sample.com/shibboleth-sp"
SPNameQualifier="https://musc.sample.com/shibboleth-sp">_d1fa6adsffsdafadf68e87</saml2:NameID>
<saml2:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml2:SubjectConfirmationData
Address="128.23.43.89" InResponseTo="_83ee90bfadsfasdfasdfffad75cb0b"
NotOnOrAfter="2012-04-06T18:18:53.300Z" Recipient="https://musc.sample.com/saml/SSOAssert.aspx" />
</saml2:SubjectConfirmation>
</saml2:Subject>
<saml2:Conditions NotBefore="2012-04-06T18:13:53.300Z"
NotOnOrAfter="2012-04-06T18:18:53.300Z">
<saml2:AudienceRestriction>
<saml2:Audience>https://musc.sample.com/shibboleth-sp
</saml2:Audience>
</saml2:AudienceRestriction>
</saml2:Conditions>
<saml2:AuthnStatement AuthnInstant="2012-04-06T18:13:53.224Z"
SessionIndex="321f70424b6bd6bbcasdfadsfsdf2cab248060b3">
<saml2:SubjectLocality Address="128.23.43.89" />
<saml2:AuthnContext>
<saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
</saml2:AuthnContextClassRef>
</saml2:AuthnContext>
</saml2:AuthnStatement>
<saml2:AttributeStatement>
<saml2:Attribute FriendlyName="uid"
Name="urn:oid:0.9.2342.19200300.100.1.1" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
<saml2:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:type="xs:string">rob</saml2:AttributeValue>
</saml2:Attribute>
<saml2:Attribute FriendlyName="sn" Name="urn:oid:2.5.4.4"
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
<saml2:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:type="xs:string">Castellow</saml2:AttributeValue>
</saml2:Attribute>
<saml2:Attribute FriendlyName="entryDN" Name="urn:oid:1.3.6.1.1.20"
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
<saml2:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:type="xs:string">uid=robC,ou=people,dc=blaco,dc=edu</saml2:AttributeValue>
</saml2:Attribute>
<saml2:Attribute FriendlyName="mail"
Name="urn:oid:0.9.2342.19200300.100.1.3" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
<saml2:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:type="xs:string">rob@blah.edu</saml2:AttributeValue>
</saml2:Attribute>
</saml2:AttributeStatement>
</saml2:Assertion>
</saml2p:Response>
SP POST Request; IdP POST Response
Source: Wikipedia.org (Tom Scavo)
SP Redirect Artifact; IdP Redirect Artifact
Source: Wikipedia.org (Tom Scavo)
Which Method Do I Use?
SAML 2.0 - You may want to use SAML 2.0 because:
- You have an explicit pre-defined relationship between your relying party and the identity provider (The relationships are extremely formal)
- You have a pre-defined list of identity providers that are federated. Company A wants to accept accounts from company B, so they federate their identity providers.
OpenID 2.0 - You may want to use OpenID 2.0 because:
- OpenID is not as formally bound to its Identity Providers, and provide more flexibility in authenticating on the web. A service provider can be created quickly and without needing to define a relationship with IdPs in advance.
- No federation between IdPs.
- You want to make it easier for users with Google email, Yahoo, etc to authenticate to your site
OAuth - You may want to use OAuth because:
- You may want to allow Twitter, Facebook, etc users to authenticate and allow service providers to have limited access to user data, files, etc.
- Millions of Facebook and Twitter users can use your site.
- It is possible to access more data from OAuth apps without asking users to fill out registration forms.
Examples
SAML 2.0 (Shibboleth) - SP Post Request - IDP Post Response, Read Attributes from Response
Perl
#!/usr/bin/perl
use Data::Dumper;
use CGI::Cookie
print "Content-Type: text/html\n\n";
print "";
%cookies = fetch CGI::Cookie;
foreach (keys %cookies) {
$cookie = $cookies{$_};
print $cookie->name . " = ";
print $cookie->value . "; ";
print $cookie->path . "; ";
print $cookie->expires ."
";
}
print << "-OUT-";
-OUT-
print "\n";
foreach $key (sort keys(%ENV)) {
print "$key = $ENV{$key}
";
}
print "";
OpenId - Create an OpenID, Login with OpenId, Enable your Site with OpenId
Create an OpenId
You may already have an OpenId if you use any of these services:
- Google
- Yahoo
- Blogspot
- Flickr
- MySpace
- Wordpress
- Chi.mp
- Facebook (? They are not a provider, but a supporter which means you can login with your OpenId but you cannot login to other sites with your Facebook Id. Facebook uses Facebook Connect)
What would I see if an application supports OpenId?
Here is how you would login with your OpenId
What would be the benefits of using OpenId?
- Lower the barrier of entry to register at your site. More conversions!
- More user information is available because the user has provided it already 1x and you are not asking them to do something that they may have already provided.
- Forget about providing password recovery services
- Allow users to publish information to other contacts or social media.
How do I enable OpenId on my website?
If you are using one of the following applications, the work has already been done for you via plugins:
- Drupal
- WordPress (OpenID)
- WordPress (Janrain Engage)
- SPIP
- WebGUI
- MediaWiki
- DokuWiki
- phpBB
- PunBB
Example:
You could also use a 3rd party hosted software to manage OpenId on your website.
Janrain Engage is one example.
Code It With Existing Libraries!
Apache 2
mod_auth_openid Using a the openid module in Apache would be used for authenticating users attempting to enter a URI within the Apach instance.
First, load the module in Apache.
# note that the path to your module might be different
LoadModule authopenid_module /usr/lib/apache2/modules/mod_auth_openid.so
Next, configure what resources are protected and specify the IdP.
Add the following to the htttpd.conf or .htaccess file.
AuthType OpenID
require valid-user
The following should be added to the httpd.conf:
AuthOpenIDDBLocation /some/location/my_file.db
AuthOpenIDTrusted ^http://myopenid.com/server$ ^http://someprovider.com/idp$
AuthOpenIDDistrusted ^http://hackerdomain ^http://openid.microsoft.com$
AuthOpenIDUseCookie Off
AuthOpenIDTrustRoot http://example.com
AuthOpenIDCookieName example_cookie_name
AuthOpenIDLoginPage /login.html
AuthOpenIDCookieLifespan 3600 # one hour
AuthOpenIDServerName http://example.com
AuthOpenIDUserProgram /path/to/authorization/program
AuthOpenIDCookiePath /path/to/protect
Java (See Example)
There are several Java implementations, but in this case, we’ll use JOpenId which we can find
here.
Download the JOpenId library.
PHP (See Example)
Ruby
Like our PHP implementation Open Enabled can be used with Ruby as well.
gem install ruby-openid
Check the installation:
$ irb
irb> require 'rubygems'
irb> require_gem 'ruby-openid'
=> true
require 'pathname'
require "openid"
require 'openid/extensions/sreg'
require 'openid/extensions/pape'
require 'openid/store/filesystem'
class ConsumerController < ApplicationController
layout nil
def index
# render an openid form
end
def start
begin
identifier = params[:openid_identifier]
if identifier.nil?
flash[:error] = "Enter an OpenID identifier"
redirect_to :action => 'index'
return
end
oidreq = consumer.begin(identifier)
rescue OpenID::OpenIDError => e
flash[:error] = "Discovery failed for #{identifier}: #{e}"
redirect_to :action => 'index'
return
end
if params[:use_sreg]
sregreq = OpenID::SReg::Request.new
# required fields
sregreq.request_fields(['email','nickname'], true)
# optional fields
sregreq.request_fields(['dob', 'fullname'], false)
oidreq.add_extension(sregreq)
oidreq.return_to_args['did_sreg'] = 'y'
end
if params[:use_pape]
papereq = OpenID::PAPE::Request.new
papereq.add_policy_uri(OpenID::PAPE::AUTH_PHISHING_RESISTANT)
papereq.max_auth_age = 2*60*60
oidreq.add_extension(papereq)
oidreq.return_to_args['did_pape'] = 'y'
end
if params[:force_post]
oidreq.return_to_args['force_post']='x'*2048
end
return_to = url_for :action => 'complete', :only_path => false
realm = url_for :action => 'index', :id => nil, :only_path => false
if oidreq.send_redirect?(realm, return_to, params[:immediate])
redirect_to oidreq.redirect_url(realm, return_to, params[:immediate])
else
render :text => oidreq.html_markup(realm, return_to, params[:immediate], {'id' => 'openid_form'})
end
end
def complete
# FIXME - url_for some action is not necessarily the current URL.
current_url = url_for(:action => 'complete', :only_path => false)
parameters = params.reject{|k,v|request.path_parameters[k]}
oidresp = consumer.complete(parameters, current_url)
case oidresp.status
when OpenID::Consumer::FAILURE
if oidresp.display_identifier
flash[:error] = ("Verification of #{oidresp.display_identifier}"\
" failed: #{oidresp.message}")
else
flash[:error] = "Verification failed: #{oidresp.message}"
end
when OpenID::Consumer::SUCCESS
flash[:success] = ("Verification of #{oidresp.display_identifier}"\
" succeeded.")
if params[:did_sreg]
sreg_resp = OpenID::SReg::Response.from_success_response(oidresp)
sreg_message = "Simple Registration data was requested"
if sreg_resp.empty?
sreg_message << ", but none was returned."
else
sreg_message << ". The following data were sent:"
sreg_resp.data.each {|k,v|
sreg_message << "<br/><b>#{k}</b>: #{v}"
}
end
flash[:sreg_results] = sreg_message
end
if params[:did_pape]
pape_resp = OpenID::PAPE::Response.from_success_response(oidresp)
pape_message = "A phishing resistant authentication method was requested"
if pape_resp.auth_policies.member? OpenID::PAPE::AUTH_PHISHING_RESISTANT
pape_message << ", and the server reported one."
else
pape_message << ", but the server did not report one."
end
if pape_resp.auth_time
pape_message << "<br><b>Authentication time:</b> #{pape_resp.auth_time} seconds"
end
if pape_resp.nist_auth_level
pape_message << "<br><b>NIST Auth Level:</b> #{pape_resp.nist_auth_level}"
end
flash[:pape_results] = pape_message
end
when OpenID::Consumer::SETUP_NEEDED
flash[:alert] = "Immediate request failed - Setup Needed"
when OpenID::Consumer::CANCEL
flash[:alert] = "OpenID transaction cancelled."
else
end
redirect_to :action => 'index'
end
private
def consumer
if @consumer.nil?
dir = Pathname.new(RAILS_ROOT).join('db').join('cstore')
store = OpenID::Store::Filesystem.new(dir)
@consumer = OpenID::Consumer.new(session, store)
end
return @consumer
end
end
How do I become an OpenId provider? (from easiest to most difficult)
- Use JanRain’s software as a service (or some other service) to create an OpenId federated provider.
- Host an implementation yourself using existing libraries. http://openid.net/developers/libraries
- Develop your own customized implementations embedded in your application.
OAuth
JavaScript (Facebook)
Facebook uses OAuth 2.0 for authentication and authorization. Here are the steps to implement authentication using OAuth in Facebook.
- Download the open source JavaScript SDK
- register your website with Facebook to get an App ID (or appId)
- Load the Javascript in your HTML
- Provide a Login for your user
- The user logs into the site and releases some basic information. By default, Facebook will give you access to the user's name, picture and any other data they have shared with everyone on Facebook
You can request additional attributes as well. If the attributes are in addition to the standard attribute list, Facebook will ask the user if they would like to allow the additional attributes to be released before authentication occurs.
<html>
<head>
<title>My Facebook Login Page</title>
</head>
<!-- More information about the Facebook API can be found at http://developers.facebook.com/docs/guides/web/ -->
<!-- To undo the settings you need to go to Facebook Privacy > Apps and Websites to Revoke Authorization -->
<body>
<div id="fb-root"></div>
<script src="js/jquery.min.js" type="text/javascript"></script>
<script>
window.fbAsyncInit = function() {
FB.init({
appId : '385811328105986',
status : true,
cookie : true,
xfbml : true,
oauth : true,
});
};
(function(d){
var js, id = 'facebook-jssdk'; if (d.getElementById(id)) {return;}
js = d.createElement('script'); js.id = id; js.async = true;
js.src = "http://connect.facebook.net/en_US/all.js";
d.getElementsByTagName('head')[0].appendChild(js);
}(document));
$(document).ready(function() {
$('span#refreshStatus').on('click', function(event) {
event.preventDefault();
refreshStatus(FB);
userInfo(FB);
});
})
function refreshStatus(FB) {
var statusText = '<b>Login status: </b><br/>';
FB.getLoginStatus(function(response) {
if (response.status === 'connected') {
var uid = response.authResponse.userID;
var accessToken = response.authResponse.accessToken;
var expiration = response.authResponse.expiresIn;
statusText += 'Logged in as: ' + uid + '<br/>';
statusText += 'Access token: ' + accessToken + '<br/>';
statusText += 'Expires in: ' + expiration ;
} else if (response.status === 'not_authorized') {
statusText += 'Not authorized';
} else {
statusText += 'Not logged into Facebook.';
}
});
statusText += '<br/><br/>';
$('#currentStatus').html(statusText);
}
function userInfo(FB){
var statusText = '<b>User Data: (You can pull whatever data the end user agrees to provide.' +
'The JSON response is included below...)</b><br/>';
FB.api('/me', function(response) {
var name = response.name ;
var email = response.email;
var responseText = response.toSource();
statusText += 'Name: ' + name + '<br/>' ;
statusText += 'Email: ' + email + '<br/><br/>';
statusText += 'Response: ' + responseText + '<br/>';
statusText += '<br/><br/>'
$('div#userStuff').html(statusText);
});
}
</script>
<h2>My BSides Facebook OAuth Demo</h2>
<p>Want In??</p>
<!-- This is where you can ask for a few things user_location,email, mail_box!!! -->
<div class="fb-login-button" scope="email,user_likes,user_location">Login to BSide
with Facebook Creds</div>
<br />
<div id="currentStatus"></div>
<br/>
<div id="userStuff"></div>
<span id="refreshStatus" style="color: #FF0000" >Refresh
Status</span>
<br />
<br />
<br />
<a href="/logout.html" onclick="FB.logout();">Logout</a>
</body>
</html>
Twitter
Twitter also uses different forms of OAuth for authentication.
Other OAuth Implementations
~