diff options
author | Andrew Arnott <andrewarnott@gmail.com> | 2009-06-05 22:23:28 -0700 |
---|---|---|
committer | Andrew Arnott <andrewarnott@gmail.com> | 2009-06-05 22:23:28 -0700 |
commit | 63daff25f5ea47e6bd7826c21fe2ba9905185e8c (patch) | |
tree | 7be29fb11774c8895050790004ab9cfb16310aa8 | |
parent | da17dd303e4dbf663882cf926678cd7794077e9f (diff) | |
download | DotNetOpenAuth-63daff25f5ea47e6bd7826c21fe2ba9905185e8c.zip DotNetOpenAuth-63daff25f5ea47e6bd7826c21fe2ba9905185e8c.tar.gz DotNetOpenAuth-63daff25f5ea47e6bd7826c21fe2ba9905185e8c.tar.bz2 |
Initial change to comply with OAuth 1.0a.
Still need to come up with a plan to handle Consumers without callback, including mobile and set-top devices with limited keyboards.
No tests yet.
Limited thought given so far to interoperability with 1.0 vs. 1.0a.
36 files changed, 2971 insertions, 98 deletions
diff --git a/doc/specs/OAuth Core 1.0a (Draft 3).htm b/doc/specs/OAuth Core 1.0a (Draft 3).htm new file mode 100644 index 0000000..9b8de42 --- /dev/null +++ b/doc/specs/OAuth Core 1.0a (Draft 3).htm @@ -0,0 +1,2477 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> +<html lang="en"><head><title>Implementer Draft: OAuth Core 1.0 Rev A (Draft 3)</title> +<meta http-equiv="Expires" content="Tue, 12 May 2009 22:56:13 +0000"> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> +<meta name="description" content="OAuth Core 1.0 Rev A (Draft 3)"> +<meta name="generator" content="xml2rfc v1.34pre3 (http://xml.resource.org/)"> +<style type='text/css'><!-- + body { + font-family: verdana, charcoal, helvetica, arial, sans-serif; + font-size: small; color: #000; background-color: #FFF; + margin: 2em; + } + h1, h2, h3, h4, h5, h6 { + font-family: helvetica, monaco, "MS Sans Serif", arial, sans-serif; + font-weight: bold; font-style: normal; + } + h1 { color: #900; background-color: transparent; text-align: right; } + h3 { color: #333; background-color: transparent; } + + td.RFCbug { + font-size: x-small; text-decoration: none; + width: 30px; height: 30px; padding-top: 2px; + text-align: justify; vertical-align: middle; + background-color: #000; + } + td.RFCbug span.RFC { + font-family: monaco, charcoal, geneva, "MS Sans Serif", helvetica, verdana, sans-serif; + font-weight: bold; color: #666; + } + td.RFCbug span.hotText { + font-family: charcoal, monaco, geneva, "MS Sans Serif", helvetica, verdana, sans-serif; + font-weight: normal; text-align: center; color: #FFF; + } + + table.TOCbug { width: 30px; height: 15px; } + td.TOCbug { + text-align: center; width: 30px; height: 15px; + color: #FFF; background-color: #900; + } + td.TOCbug a { + font-family: monaco, charcoal, geneva, "MS Sans Serif", helvetica, sans-serif; + font-weight: bold; font-size: x-small; text-decoration: none; + color: #FFF; background-color: transparent; + } + + td.header { + font-family: arial, helvetica, sans-serif; font-size: x-small; + vertical-align: top; width: 33%; + color: #FFF; background-color: #666; + } + td.author { font-weight: bold; font-size: x-small; margin-left: 4em; } + td.author-text { font-size: x-small; } + + /* info code from SantaKlauss at http://www.madaboutstyle.com/tooltip2.html */ + a.info { + /* This is the key. */ + position: relative; + z-index: 24; + text-decoration: none; + } + a.info:hover { + z-index: 25; + color: #FFF; background-color: #900; + } + a.info span { display: none; } + a.info:hover span.info { + /* The span will display just on :hover state. */ + display: block; + position: absolute; + font-size: smaller; + top: 2em; left: -5em; width: 15em; + padding: 2px; border: 1px solid #333; + color: #900; background-color: #EEE; + text-align: left; + } + + a { font-weight: bold; } + a:link { color: #900; background-color: transparent; } + a:visited { color: #633; background-color: transparent; } + a:active { color: #633; background-color: transparent; } + + p { margin-left: 2em; margin-right: 2em; } + p.copyright { font-size: x-small; } + p.toc { font-size: small; font-weight: bold; margin-left: 3em; } + table.toc { margin: 0 0 0 3em; padding: 0; border: 0; vertical-align: text-top; } + td.toc { font-size: small; font-weight: bold; vertical-align: text-top; } + + ol.text { margin-left: 2em; margin-right: 2em; } + ul.text { margin-left: 2em; margin-right: 2em; } + li { margin-left: 3em; } + + /* RFC-2629 <spanx>s and <artwork>s. */ + em { font-style: italic; } + strong { font-weight: bold; } + dfn { font-weight: bold; font-style: normal; } + cite { font-weight: normal; font-style: normal; } + tt { color: #036; } + tt, pre, pre dfn, pre em, pre cite, pre span { + font-family: "Courier New", Courier, monospace; font-size: small; + } + pre { + text-align: left; padding: 4px; + color: #000; background-color: #CCC; + } + pre dfn { color: #900; } + pre em { color: #66F; background-color: #FFC; font-weight: normal; } + pre .key { color: #33C; font-weight: bold; } + pre .id { color: #900; } + pre .str { color: #000; background-color: #CFF; } + pre .val { color: #066; } + pre .rep { color: #909; } + pre .oth { color: #000; background-color: #FCF; } + pre .err { background-color: #FCC; } + + /* RFC-2629 <texttable>s. */ + table.all, table.full, table.headers, table.none { + font-size: small; text-align: center; border-width: 2px; + vertical-align: top; border-collapse: collapse; + } + table.all, table.full { border-style: solid; border-color: black; } + table.headers, table.none { border-style: none; } + th { + font-weight: bold; border-color: black; + border-width: 2px 2px 3px 2px; + } + table.all th, table.full th { border-style: solid; } + table.headers th { border-style: none none solid none; } + table.none th { border-style: none; } + table.all td { + border-style: solid; border-color: #333; + border-width: 1px 2px; + } + table.full td, table.headers td, table.none td { border-style: none; } + + hr { height: 1px; } + hr.insert { + width: 80%; border-style: none; border-width: 0; + color: #CCC; background-color: #CCC; + } +--></style> +</head> +<body> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<table summary="layout" width="66%" border="0" cellpadding="0" cellspacing="0"><tr><td><table summary="layout" width="100%" border="0" cellpadding="2" cellspacing="1"> +<tr><td class="header">Implementer Draft</td><td class="header"> OAuth</td></tr> +<tr><td class="header"> </td><td class="header">May 12, 2009</td></tr> +</table></td></tr></table> +<h1><br />OAuth Core 1.0 Rev A (Draft 3)</h1> + +<h3>Abstract</h3> + +<p> + The OAuth protocol enables websites or applications (Consumers) to + access Protected Resources from a web service (Service Provider) via an + API, without requiring Users to disclose their Service Provider + credentials to the Consumers. More generally, OAuth creates a + freely-implementable and generic methodology for API authentication. + +</p> +<p> + An example use case is allowing printing service printer.example.com + (the Consumer), to access private photos stored on photos.example.net + (the Service Provider) without requiring Users to provide their + photos.example.net credentials to printer.example.com. + +</p> +<p> + OAuth does not require a specific user interface or interaction + pattern, nor does it specify how Service Providers authenticate Users, + making the protocol ideally suited for cases where authentication + credentials are unavailable to the Consumer, such as with OpenID. + +</p> +<p> + OAuth aims to unify the experience and implementation of delegated web + service authentication into a single, community-driven protocol. OAuth + builds on existing protocols and best practices that have been + independently implemented by various websites. An open standard, + supported by large and small providers alike, promotes a consistent and + trusted experience for both application developers and the users of + those applications. + +</p><a name="toc"></a><br /><hr /> +<h3>Table of Contents</h3> +<p class="toc"> +<a href="#anchor1">1.</a> +Authors<br /> +<a href="#anchor2">2.</a> +Notation and Conventions<br /> +<a href="#anchor3">3.</a> +Definitions<br /> +<a href="#anchor4">4.</a> +Documentation and Registration<br /> + <a href="#request_urls">4.1.</a> +Request URLs<br /> + <a href="#anchor5">4.2.</a> +Service Providers<br /> + <a href="#anchor6">4.3.</a> +Consumers<br /> +<a href="#anchor7">5.</a> +Parameters<br /> + <a href="#encoding_parameters">5.1.</a> +Parameter Encoding<br /> + <a href="#consumer_req_param">5.2.</a> +Consumer Request Parameters<br /> + <a href="#response_parameters">5.3.</a> +Service Provider Response Parameters<br /> + <a href="#auth_header">5.4.</a> +OAuth HTTP Authorization Scheme<br /> +<a href="#anchor9">6.</a> +Authenticating with OAuth<br /> + <a href="#auth_step1">6.1.</a> +Obtaining an Unauthorized Request Token<br /> + <a href="#auth_step2">6.2.</a> +Obtaining User Authorization<br /> + <a href="#auth_step3">6.3.</a> +Obtaining an Access Token<br /> +<a href="#anchor12">7.</a> +Accessing Protected Resources<br /> +<a href="#nonce">8.</a> +Nonce and Timestamp<br /> +<a href="#signing_process">9.</a> +Signing Requests<br /> + <a href="#anchor13">9.1.</a> +Signature Base String<br /> + <a href="#anchor15">9.2.</a> +HMAC-SHA1<br /> + <a href="#anchor18">9.3.</a> +RSA-SHA1<br /> + <a href="#anchor21">9.4.</a> +PLAINTEXT<br /> +<a href="#http_codes">10.</a> +HTTP Response Codes<br /> +<a href="#anchor24">11.</a> +Security Considerations<br /> + <a href="#anchor25">11.1.</a> +Credentials and Token Exchange<br /> + <a href="#anchor26">11.2.</a> +PLAINTEXT Signature Method<br /> + <a href="#anchor27">11.3.</a> +Confidentiality of Requests<br /> + <a href="#anchor28">11.4.</a> +Spoofing by Counterfeit Servers<br /> + <a href="#anchor29">11.5.</a> +Proxying and Caching of Authenticated Content<br /> + <a href="#anchor30">11.6.</a> +Plaintext Storage of Credentials<br /> + <a href="#anchor31">11.7.</a> +Secrecy of the Consumer Secret<br /> + <a href="#anchor32">11.8.</a> +Phishing Attacks<br /> + <a href="#anchor33">11.9.</a> +Scoping of Access Requests<br /> + <a href="#anchor34">11.10.</a> +Entropy of Secrets<br /> + <a href="#anchor35">11.11.</a> +Denial of Service / Resource Exhaustion Attacks<br /> + <a href="#anchor36">11.12.</a> +Cryptographic Attacks<br /> + <a href="#anchor37">11.13.</a> +Signature Base String Compatibility<br /> + <a href="#anchor38">11.14.</a> +Cross-Site Request Forgery (CSRF)<br /> + <a href="#anchor39">11.15.</a> +User Interface Redress<br /> + <a href="#anchor40">11.16.</a> +Automatic Processing of Repeat Authorizations<br /> +<a href="#anchor41">Appendix A.</a> +Appendix A - Protocol Example<br /> +<a href="#anchor42">Appendix A.1.</a> +Documentation and Registration<br /> +<a href="#anchor43">Appendix A.2.</a> +Obtaining a Request Token<br /> +<a href="#anchor44">Appendix A.3.</a> +Requesting User Authorization<br /> +<a href="#anchor45">Appendix A.4.</a> +Obtaining an Access Token<br /> +<a href="#anchor46">Appendix A.5.</a> +Accessing Protected Resources<br /> +<a href="#rfc.references1">12.</a> +References<br /> +<a href="#rfc.authors">§</a> +Author's Address<br /> +</p> +<br clear="all" /> + +<a name="anchor1"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.1"></a><h3>1. +Authors</h3> + +<p> + </p> +<blockquote class="text"> +<p>Mark Atwood (me@mark.atwood.name) +</p> +<p>Dirk Balfanz (balfanz@google.com) +</p> +<p>Darren Bounds (darren@cliqset.com) +</p> +<p>Richard M. Conlan (zeveck@google.com) +</p> +<p>Blaine Cook (blaine@twitter.com) +</p> +<p>Leah Culver (leah@pownce.com) +</p> +<p>Breno de Medeiros (breno@google.com) +</p> +<p>Brian Eaton (beaton@google.com) +</p> +<p>Kellan Elliott-McCrea (kellan@flickr.com) +</p> +<p>Larry Halff (larry@ma.gnolia.com) +</p> +<p>Eran Hammer-Lahav (eran@hueniverse.com), Editor +</p> +<p>Ben Laurie (benl@google.com) +</p> +<p>Chris Messina (chris@citizenagency.com) +</p> +<p>John Panzer (jpanzer@acm.org) +</p> +<p>Sam Quigley (quigley@emerose.com) +</p> +<p>David Recordon (david@sixapart.com) +</p> +<p>Eran Sandler (eran@yedda.com) +</p> +<p>Jonathan Sergent (sergent@google.com) +</p> +<p>Todd Sieling (todd@ma.gnolia.com) +</p> +<p>Brian Slesinsky (brian-oauth@slesinsky.org) +</p> +<p>Andy Smith (andy@jaiku.com) +</p> +</blockquote><p> + +</p> +<a name="anchor2"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.2"></a><h3>2. +Notation and Conventions</h3> + +<p> + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this + document are to be interpreted as described in <a class='info' href='#RFC2119'>[RFC2119]<span> (</span><span class='info'>Bradner, B., “Key words for use in RFCs to Indicate Requirement Levels,” .</span><span>)</span></a>. + Domain name examples use <a class='info' href='#RFC2606'>[RFC2606]<span> (</span><span class='info'>Eastlake, D. and A. Panitz, “Reserved Top Level DNS Names,” .</span><span>)</span></a>. + +</p> +<a name="anchor3"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.3"></a><h3>3. +Definitions</h3> + +<p> + </p> +<blockquote class="text"><dl> +<dt>Service Provider:</dt> +<dd> + A web application that allows access via OAuth. + +</dd> +<dt>User:</dt> +<dd> + An individual who has an account with the Service Provider. + +</dd> +<dt>Consumer:</dt> +<dd> + A website or application that uses OAuth to access the Service + Provider on behalf of the User. + +</dd> +<dt>Protected Resource(s):</dt> +<dd> + Data controlled by the Service Provider, which the Consumer can + access through authentication. + +</dd> +<dt>Consumer Developer:</dt> +<dd> + An individual or organization that implements a Consumer. + +</dd> +<dt>Consumer Key:</dt> +<dd> + A value used by the Consumer to identify itself to the Service + Provider. + +</dd> +<dt>Consumer Secret:</dt> +<dd> + A secret used by the Consumer to establish ownership of the + Consumer Key. + +</dd> +<dt>Request Token:</dt> +<dd> + A value used by the Consumer to obtain authorization from the User, + and exchanged for an Access Token. + +</dd> +<dt>Access Token:</dt> +<dd> + A value used by the Consumer to gain access to the Protected + Resources on behalf of the User, instead of using the User's + Service Provider credentials. + +</dd> +<dt>Token Secret:</dt> +<dd> + A secret used by the Consumer to establish ownership of a given + Token. + +</dd> +<dt>OAuth Protocol Parameters:</dt> +<dd> + Parameters with names beginning with <tt>oauth_</tt>. + +</dd> +</dl></blockquote><p> + +</p> +<a name="anchor4"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.4"></a><h3>4. +Documentation and Registration</h3> + +<p> + OAuth includes a Consumer Key and matching Consumer Secret that + together authenticate the Consumer (as opposed to the User) to the + Service Provider. Consumer-specific identification allows the Service + Provider to vary access levels to Consumers (such as un-throttled access + to resources). + +</p> +<p> + Service Providers SHOULD NOT rely on the Consumer Secret as a method to + verify the Consumer identity, unless the Consumer Secret is known to be + inaccessible to anyone other than the Consumer and the Service + Provider. The Consumer Secret MAY be an empty string (for example when + no Consumer verification is needed, or when verification is achieved + through other means such as RSA). + +</p> +<a name="request_urls"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.4.1"></a><h3>4.1. +Request URLs</h3> + +<p> + OAuth defines three request URLs: + + </p> +<blockquote class="text"><dl> +<dt>Request Token URL:</dt> +<dd> + The URL used to obtain an unauthorized Request Token, described + in <a class='info' href='#auth_step1'>Section 6.1<span> (</span><span class='info'>Obtaining an Unauthorized Request Token</span><span>)</span></a>. + +</dd> +<dt>User Authorization URL:</dt> +<dd> + The URL used to obtain User authorization for Consumer access, + described in <a class='info' href='#auth_step2'>Section 6.2<span> (</span><span class='info'>Obtaining User Authorization</span><span>)</span></a>. + +</dd> +<dt>Access Token URL:</dt> +<dd> + The URL used to exchange the User-authorized Request Token for + an Access Token, described in <a class='info' href='#auth_step3'>Section 6.3<span> (</span><span class='info'>Obtaining an Access Token</span><span>)</span></a>. + +</dd> +</dl></blockquote><p> + +</p> +<p> + The three URLs MUST include scheme, authority, and path, and MAY + include query and fragment as defined by <a class='info' href='#RFC3986'>[RFC3986]<span> (</span><span class='info'>Berners-Lee, T., “Uniform Resource Identifiers (URI): Generic Syntax,” .</span><span>)</span></a> + section 3. The request URL query MUST NOT contain any OAuth Protocol + Parameters. For example: + + </p> +<div style='display: table; width: 0; margin-left: 3em; margin-right: auto'><pre> + http://sp.example.com/authorize +</pre></div><p> + + +</p> +<a name="anchor5"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.4.2"></a><h3>4.2. +Service Providers</h3> + +<p> + The Service Provider's responsibility is to enable Consumer Developers + to establish a Consumer Key and Consumer Secret. The process and + requirements for provisioning these are entirely up to the Service + Providers. + +</p> +<p> + The Service Provider's documentation includes: + + </p> +<ol class="text"> +<li> + The <a class='info' href='#request_urls'>URLs<span> (</span><span class='info'>Request URLs</span><span>)</span></a> the Consumer will + use when making OAuth requests, and the HTTP methods (i.e. GET, + POST, etc.) used in the Request Token URL and Access Token URL. + +</li> +<li> + Signature methods supported by the Service Provider. + +</li> +<li> + Any additional request parameters that the Service Provider + requires in order to obtain a Token. Service Provider specific + parameters MUST NOT begin with <tt>oauth_</tt>. + +</li> +</ol><p> + +</p> +<a name="anchor6"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.4.3"></a><h3>4.3. +Consumers</h3> + +<p> + The Consumer Developer MUST establish a Consumer Key and a Consumer + Secret with the Service Provider. The Consumer Developer MAY also be + required to provide additional information to the Service Provider + upon registration. + +</p> +<a name="anchor7"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.5"></a><h3>5. +Parameters</h3> + +<p> + OAuth Protocol Parameter names and values are case sensitive. Each + OAuth Protocol Parameters MUST NOT appear more than once per request, + and are REQUIRED unless otherwise noted. + +</p> +<a name="encoding_parameters"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.5.1"></a><h3>5.1. +Parameter Encoding</h3> + +<p> + All parameter names and values are escaped using the + <a class='info' href='#RFC3986'>[RFC3986]<span> (</span><span class='info'>Berners-Lee, T., “Uniform Resource Identifiers (URI): Generic Syntax,” .</span><span>)</span></a> percent-encoding (%xx) mechanism. + Characters not in the unreserved character set + (<a class='info' href='#RFC3986'>[RFC3986]<span> (</span><span class='info'>Berners-Lee, T., “Uniform Resource Identifiers (URI): Generic Syntax,” .</span><span>)</span></a> section 2.3) MUST be encoded. Characters + in the unreserved character set MUST NOT be encoded. Hexadecimal + characters in encodings MUST be upper case. Text names and values + MUST be encoded as UTF-8 octets before percent-encoding them per + <a class='info' href='#RFC3629'>[RFC3629]<span> (</span><span class='info'>Yergeau, F., “UTF-8, a transformation format of Unicode and ISO 10646,” .</span><span>)</span></a>. + +</p><div style='display: table; width: 0; margin-left: 3em; margin-right: auto'><pre> + unreserved = ALPHA, DIGIT, '-', '.', '_', '~' +</pre></div> +<a name="consumer_req_param"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.5.2"></a><h3>5.2. +Consumer Request Parameters</h3> + +<p> + OAuth Protocol Parameters are sent from the Consumer to the Service + Provider in one of three methods, in order of decreasing preference: + </p> +<ol class="text"> +<li> + In the HTTP <tt>Authorization</tt> header as defined in + <a class='info' href='#auth_header'>OAuth HTTP Authorization Scheme<span> (</span><span class='info'>OAuth HTTP Authorization Scheme</span><span>)</span></a>. + +</li> +<li> + As the HTTP POST request body with a <tt> + content-type + </tt> of + <tt>application/x-www-form-urlencoded</tt>. + +</li> +<li> + Added to the URLs in the query part (as defined by + <a class='info' href='#RFC3986'>[RFC3986]<span> (</span><span class='info'>Berners-Lee, T., “Uniform Resource Identifiers (URI): Generic Syntax,” .</span><span>)</span></a> section 3). + +</li> +</ol><p> + +</p> +<p> + In addition to these defined methods, future extensions may describe + alternate methods for sending the OAuth Protocol Parameters. + The methods for sending other request parameters are left + undefined, but SHOULD NOT use the + <a class='info' href='#auth_header'>OAuth HTTP Authorization Scheme<span> (</span><span class='info'>OAuth HTTP Authorization Scheme</span><span>)</span></a> header. + +</p> +<a name="response_parameters"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.5.3"></a><h3>5.3. +Service Provider Response Parameters</h3> + +<p> + Response parameters are sent by the Service + Provider to return Tokens and other information to the Consumer in + the HTTP response body. The parameter names and values are first + encoded as per <a class='info' href='#encoding_parameters'>Parameter Encoding<span> (</span><span class='info'>Parameter Encoding</span><span>)</span></a>, and concatenated with the '&' character (ASCII code 38) + as defined in <a class='info' href='#RFC3986'>[RFC3986]<span> (</span><span class='info'>Berners-Lee, T., “Uniform Resource Identifiers (URI): Generic Syntax,” .</span><span>)</span></a> Section 2.1. For example: + +</p><div style='display: table; width: 0; margin-left: 3em; margin-right: auto'><pre> + oauth_token=ab3cd9j4ks73hf7g&oauth_token_secret=xyz4992k83j47x0b +</pre></div> +<a name="auth_header"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.5.4"></a><h3>5.4. +OAuth HTTP Authorization Scheme</h3> + +<p> + This section defines an <a class='info' href='#RFC2617'>[RFC2617]<span> (</span><span class='info'>Franks, J., Hallam-Baker, P., Hostetler, J., Lawrence, S., Leach, P., Luotonen, A., and L. Stewart, “HTTP Authentication: Basic and Digest Access Authentication,” .</span><span>)</span></a> extension to + support OAuth. It uses the standard HTTP <tt>Authorization</tt> and + <tt>WWW-Authenticate</tt> headers to pass OAuth Protocol Parameters. + +</p> +<p> + It is RECOMMENDED that Service Providers accept the HTTP + <tt>Authorization</tt> header. Consumers SHOULD be able to send OAuth + Protocol Parameters in the OAuth <tt>Authorization</tt> header. + +</p> +<p> + The extension auth-scheme (as defined by + <a class='info' href='#RFC2617'>[RFC2617]<span> (</span><span class='info'>Franks, J., Hallam-Baker, P., Hostetler, J., Lawrence, S., Leach, P., Luotonen, A., and L. Stewart, “HTTP Authentication: Basic and Digest Access Authentication,” .</span><span>)</span></a>) is <tt>OAuth</tt> and is case-insensitive. + +</p> +<a name="auth_header_authorization"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.5.4.1"></a><h3>5.4.1. +Authorization Header</h3> + +<p> + The OAuth Protocol Parameters are sent in the <tt>Authorization</tt> + header the following way: + + </p> +<ol class="text"> +<li> + Parameter names and values are encoded per + <a class='info' href='#encoding_parameters'>Parameter Encoding<span> (</span><span class='info'>Parameter Encoding</span><span>)</span></a>. + +</li> +<li> + For each parameter, the name is immediately followed by an '=' + character (ASCII code 61), a '"' character (ASCII code 34), the + parameter value (MAY be empty), and another '"' character + (ASCII code 34). + +</li> +<li> + Parameters are separated by a comma character (ASCII code 44) + and OPTIONAL linear whitespace per <a class='info' href='#RFC2617'>[RFC2617]<span> (</span><span class='info'>Franks, J., Hallam-Baker, P., Hostetler, J., Lawrence, S., Leach, P., Luotonen, A., and L. Stewart, “HTTP Authentication: Basic and Digest Access Authentication,” .</span><span>)</span></a>. + +</li> +<li> + The OPTIONAL <tt>realm</tt> parameter is added and interpreted per + <a class='info' href='#RFC2617'>[RFC2617]<span> (</span><span class='info'>Franks, J., Hallam-Baker, P., Hostetler, J., Lawrence, S., Leach, P., Luotonen, A., and L. Stewart, “HTTP Authentication: Basic and Digest Access Authentication,” .</span><span>)</span></a>, section 1.2. + +</li> +</ol><p> + +</p> +<p> + For example: + </p> +<div style='display: table; width: 0; margin-left: 3em; margin-right: auto'><pre> + Authorization: OAuth realm="http://sp.example.com/", + oauth_consumer_key="0685bd9184jfhq22", + oauth_token="ad180jjd733klru7", + oauth_signature_method="HMAC-SHA1", + oauth_signature="wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D", + oauth_timestamp="137131200", + oauth_nonce="4572616e48616d6d65724c61686176", + oauth_version="1.0" +</pre></div><p> + + +</p> +<a name="anchor8"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.5.4.2"></a><h3>5.4.2. +WWW-Authenticate Header</h3> + +<p> + Service Providers MAY indicate their support for the extension by + returning the OAuth HTTP <tt>WWW-Authenticate</tt> + header upon Consumer requests for Protected Resources. As per + <a class='info' href='#RFC2617'>[RFC2617]<span> (</span><span class='info'>Franks, J., Hallam-Baker, P., Hostetler, J., Lawrence, S., Leach, P., Luotonen, A., and L. Stewart, “HTTP Authentication: Basic and Digest Access Authentication,” .</span><span>)</span></a> such a response MAY include additional + HTTP <tt>WWW-Authenticate</tt> headers: + +</p> +<p> + For example: + </p> +<div style='display: table; width: 0; margin-left: 3em; margin-right: auto'><pre> + WWW-Authenticate: OAuth realm="http://sp.example.com/" +</pre></div><p> + + +</p> +<p> + The realm parameter defines a protection realm per + <a class='info' href='#RFC2617'>[RFC2617]<span> (</span><span class='info'>Franks, J., Hallam-Baker, P., Hostetler, J., Lawrence, S., Leach, P., Luotonen, A., and L. Stewart, “HTTP Authentication: Basic and Digest Access Authentication,” .</span><span>)</span></a>, section 1.2. + +</p> +<a name="anchor9"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.6"></a><h3>6. +Authenticating with OAuth</h3> + +<p> + OAuth authentication is the process in which Users grant access to + their Protected Resources without sharing their credentials with the + Consumer. OAuth uses Tokens generated by the Service Provider instead + of the User's credentials in Protected Resources requests. The process + uses two Token types: + + </p> +<blockquote class="text"><dl> +<dt>Request Token:</dt> +<dd> + Used by the Consumer to ask the User to authorize access to the + Protected Resources. The User-authorized Request Token is exchanged + for an Access Token, MUST only be used once, and MUST NOT be used + for any other purpose. It is RECOMMENDED that Request Tokens have + a limited lifetime. + +</dd> +<dt>Access Token:</dt> +<dd> + Used by the Consumer to access the Protected Resources on behalf of + the User. Access Tokens MAY limit access to certain Protected + Resources, and MAY have a limited lifetime. Service Providers + SHOULD allow Users to revoke Access Tokens. Only the Access Token + SHALL be used to access the Protect Resources. + +</dd> +</dl></blockquote><p> + +</p> +<p> + OAuth Authentication is done in three steps: + + </p> +<ol class="text"> +<li> + The Consumer obtains an unauthorized Request Token. + +</li> +<li> + The User authorizes the Request Token. + +</li> +<li> + The Consumer exchanges the Request Token for an Access Token. + +</li> +</ol><p> + +</p> +<a name="auth_step1"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.6.1"></a><h3>6.1. +Obtaining an Unauthorized Request Token</h3> + +<p> + The Consumer obtains an unauthorized Request Token by asking the + Service Provider to issue a Token. The Request Token's sole purpose + is to receive User approval and can only be used to obtain an Access + Token. The Request Token process goes as follows: + +</p> +<a name="obtain_request_token"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.6.1.1"></a><h3>6.1.1. +Consumer Obtains a Request Token</h3> + +<p> + To obtain a Request Token, the Consumer sends an HTTP request to + the Service Provider's Request Token URL. The Service Provider + documentation specifies the HTTP method for this request, and HTTP POST + is RECOMMENDED. The request MUST be signed and contains the following parameters: + + </p> +<blockquote class="text"><dl> +<dt>oauth_consumer_key:</dt> +<dd> + The Consumer Key. + +</dd> +<dt>oauth_signature_method:</dt> +<dd> + The signature method the Consumer used to sign the request. + +</dd> +<dt>oauth_signature:</dt> +<dd> + The signature as defined in + <a class='info' href='#signing_process'>Signing Requests<span> (</span><span class='info'>Signing Requests</span><span>)</span></a>. + +</dd> +<dt>oauth_timestamp:</dt> +<dd> + As defined in <a class='info' href='#nonce'>Nonce and Timestamp<span> (</span><span class='info'>Nonce and Timestamp</span><span>)</span></a>. + +</dd> +<dt>oauth_nonce:</dt> +<dd> + As defined in <a class='info' href='#nonce'>Nonce and Timestamp<span> (</span><span class='info'>Nonce and Timestamp</span><span>)</span></a>. + +</dd> +<dt>oauth_version:</dt> +<dd> + OPTIONAL. If present, value MUST be <tt> + 1.0 + </tt>. Service Providers + MUST assume the protocol version to be <tt>1.0</tt> if this parameter + is not present. Service Providers' response to non-<tt>1.0</tt> value + is left undefined. + +</dd> +<dt>oauth_callback:</dt> +<dd> + An absolute URL to which the Service Provider will redirect the User back when the + <a class='info' href='#auth_step2'>Obtaining User Authorization<span> (</span><span class='info'>Obtaining User Authorization</span><span>)</span></a> step is completed. If the + Consumer is unable to receive callbacks or a callback URL has been established via other + means, the parameter value MUST be set to <tt>oob</tt> (case sensitive), + to indicate an out-of-band configuration. + +</dd> +<dt>Additional parameters:</dt> +<dd> + Any additional parameters, as defined by the Service Provider. + +</dd> +</dl></blockquote><p> + +</p> +<a name="request_grant"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.6.1.2"></a><h3>6.1.2. +Service Provider Issues an Unauthorized Request Token</h3> + +<p> + The Service Provider verifies the signature and Consumer Key. If + successful, it generates a Request Token and Token Secret and + returns them to the Consumer in the HTTP response body as defined + in <a class='info' href='#response_parameters'>Service Provider Response Parameters<span> (</span><span class='info'>Service Provider Response Parameters</span><span>)</span></a>. + The Service Provider MUST ensure the Request + Token cannot be exchanged for an Access Token until the User + successfully grants access in <a class='info' href='#auth_step2'>Obtaining + User Authorization<span> (</span><span class='info'>Obtaining User Authorization</span><span>)</span></a>. + +</p> +<p> + The response contains the following parameters: + + </p> +<blockquote class="text"><dl> +<dt>oauth_token:</dt> +<dd> + The Request Token. + +</dd> +<dt>oauth_token_secret:</dt> +<dd> + The Token Secret. + +</dd> +<dt>oauth_callback_confirmed:</dt> +<dd> + MUST be present and set to <tt>true</tt>. The Consumer + MAY use this to confirm that the Service Provider received the callback value. + +</dd> +<dt>Additional parameters:</dt> +<dd> + Any additional parameters, as defined by the Service Provider. + +</dd> +</dl></blockquote><p> + +</p> +<p> + If the request fails verification or is rejected for other reasons, + the Service Provider SHOULD respond with the appropriate response + code as defined in <a class='info' href='#http_codes'>HTTP Response Codes<span> (</span><span class='info'>HTTP Response Codes</span><span>)</span></a>. + The Service Provider MAY include some further details about why the + request was rejected in the HTTP response body as defined in + <a class='info' href='#response_parameters'>Service Provider Response Parameters<span> (</span><span class='info'>Service Provider Response Parameters</span><span>)</span></a>. + +</p> +<a name="auth_step2"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.6.2"></a><h3>6.2. +Obtaining User Authorization</h3> + +<p> + The Consumer cannot use the Request Token until it has been + authorized by the User. Obtaining User authorization includes + the following steps: + +</p> +<a name="user_auth_redirected"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.6.2.1"></a><h3>6.2.1. +Consumer Directs the User to the Service Provider</h3> + +<p> + In order for the Consumer to be able to exchange the Request Token + for an Access Token, the Consumer MUST obtain approval from the + User by directing the User to the Service Provider. The Consumer + constructs an HTTP GET request to the Service Provider's + User Authorization URL with the following parameter: + + </p> +<blockquote class="text"><dl> +<dt>oauth_token:</dt> +<dd> + OPTIONAL. The Request Token obtained in the previous step. The + Service Provider MAY declare this parameter as REQUIRED, or + accept requests to the User Authorization URL without it, in + which case it will prompt the User to enter it manually. + +</dd> +<dt>Additional parameters:</dt> +<dd> + Any additional parameters, as defined by the Service Provider. + +</dd> +</dl></blockquote><p> + +</p> +<p> + Once the request URL has been constructed the Consumer redirects + the User to the URL via the User's web browser. If the Consumer is + incapable of automatic HTTP redirection, the Consumer SHALL notify + the User how to manually go to the constructed request URL. + +</p> +<p> + Note: If a Service Provider knows a Consumer to be running on a + mobile device or set-top box, the Service Provider SHOULD ensure + that the User Authorization URL and Request Token are suitable + for manual entry. + +</p> +<a name="anchor10"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.6.2.2"></a><h3>6.2.2. +Service Provider Authenticates the User and Obtains Consent</h3> + +<p> + The Service Provider verifies the User's identity and asks for + consent as detailed. OAuth does not specify how the Service Provider + authenticates the User. However, it does define a set of REQUIRED + steps: + + </p> +<ul class="text"> +<li> + The Service Provider MUST first verify the User's identity + before asking for consent. It MAY prompt the User to sign + in if the User has not already done so. + +</li> +<li> + The Service Provider presents to the User information about the + Consumer requesting access (as registered by the Consumer + Developer). The information includes the duration of the + access and the Protected Resources provided. The information + MAY include other details specific to the Service Provider. + +</li> +<li> + The User MUST grant or deny permission for the Service Provider + to give the Consumer access to the Protected Resources on + behalf of the User. If the User denies the Consumer access, the + Service Provider MUST NOT allow access to the Protected + Resources. + +</li> +</ul><p> + +</p> +<p> + When displaying any identifying information about the Consumer to + the User based on the Consumer Key, the Service Provider MUST + inform the User if it is unable to assure the Consumer's true + identity. The method in which the Service Provider informs the User + and the quality of the identity assurance is beyond the scope of + this specification. + +</p> +<a name="provider_redirects"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.6.2.3"></a><h3>6.2.3. +Service Provider Directs the User Back to the Consumer</h3> + +<p> + After the User authenticates with the Service Provider and grants + permission for Consumer access, the Consumer MUST be notified that + the Request Token has been authorized and ready to be exchanged for + an Access Token. If the User denies access, the Consumer MAY be + notified that the Request Token has been revoked. + +</p> +<p> + To make sure that the User granting access is the same User returning + back to the Consumer to complete the process, the Service Provider MUST + generate a verification code: an unguessable value passed to the Consumer via the + User and REQUIRED to complete the process. + +</p> +<p> + If the Consumer provided a callback URL (using the <tt>oauth_callback</tt> + parameter in <a class='info' href='#obtain_request_token'>Section 6.1.1<span> (</span><span class='info'>Consumer Obtains a Request Token</span><span>)</span></a> or by other means), the Service Provider uses + it to constructs an HTTP request, and directs the User's web browser to that URL with the following + parameters added: + + </p> +<blockquote class="text"><dl> +<dt>oauth_token:</dt> +<dd> + The Request Token the User authorized or denied. + +</dd> +<dt>oauth_verifier:</dt> +<dd> + The verification code. + +</dd> +</dl></blockquote><p> + +</p> +<p> + The callback URL MAY include Consumer provided query parameters. + The Service Provider MUST retain them unmodified and append the + <tt>oauth_token</tt> parameter to the existing query. + +</p> +<p> + If the Consumer did not provide a callback URL, the Service Provider MUST display the value of the + verification code, and instruct the User to manually inform the Consumer that authorization is completed. If the Service Provider + knows a Consumer to be running on a mobile device or set-top box, the Service Provider + SHOULD ensure that the verifier value is suitable for manual entry. + +</p> +<a name="auth_step3"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.6.3"></a><h3>6.3. +Obtaining an Access Token</h3> + +<p> + The Consumer exchanges the Request Token for an Access Token capable + of accessing the Protected Resources. Obtaining an Access Token + includes the following steps: + +</p> +<a name="anchor11"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.6.3.1"></a><h3>6.3.1. +Consumer Requests an Access Token</h3> + +<p> + The Request Token and Token Secret MUST be exchanged for an Access + Token and Token Secret. + +</p> +<p> + To request an Access Token, the Consumer makes an HTTP request to + the Service Provider's Access Token URL. The Service Provider + documentation specifies the HTTP method for this request, and HTTP POST + is RECOMMENDED. The request MUST be signed per + <a class='info' href='#signing_process'>Signing Requests<span> (</span><span class='info'>Signing Requests</span><span>)</span></a>, + and contains the following parameters: + + </p> +<blockquote class="text"><dl> +<dt>oauth_consumer_key:</dt> +<dd> + The Consumer Key. + +</dd> +<dt>oauth_token:</dt> +<dd> + The Request Token obtained previously. + +</dd> +<dt>oauth_signature_method:</dt> +<dd> + The signature method the Consumer used to sign the request. + +</dd> +<dt>oauth_signature:</dt> +<dd> + The signature as defined in <a class='info' href='#signing_process'>Signing Requests<span> (</span><span class='info'>Signing Requests</span><span>)</span></a>. + +</dd> +<dt>oauth_timestamp:</dt> +<dd> + As defined in <a class='info' href='#nonce'>Nonce and Timestamp<span> (</span><span class='info'>Nonce and Timestamp</span><span>)</span></a>. + +</dd> +<dt>oauth_nonce:</dt> +<dd> + As defined in <a class='info' href='#nonce'>Nonce and Timestamp<span> (</span><span class='info'>Nonce and Timestamp</span><span>)</span></a>. + +</dd> +<dt>oauth_version:</dt> +<dd> + OPTIONAL. If present, value MUST be <tt> + 1.0 + </tt>. Service Providers + MUST assume the protocol version to be <tt>1.0</tt> if this parameter + is not present. Service Providers' response to non-<tt>1.0</tt> value + is left undefined. + +</dd> +<dt>oauth_verifier:</dt> +<dd> + The verification code received from the Service Provider in the + <a class='info' href='#provider_redirects'>Service Provider Directs the User Back to the Consumer<span> (</span><span class='info'>Service Provider Directs the User Back to the Consumer</span><span>)</span></a> step. + +</dd> +</dl></blockquote><p> + +</p> +<p> + No additional Service Provider specific parameters are allowed when + requesting an Access Token to ensure all Token related information + is present prior to seeking User approval. + +</p> +<a name="access_grant"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.6.3.2"></a><h3>6.3.2. +Service Provider Grants an Access Token</h3> + +<p> + The Service Provider MUST ensure that: + + </p> +<ul class="text"> +<li> + The request signature has been successfully verified. + +</li> +<li> + The Request Token has never been exchanged for an Access Token. + +</li> +<li> + The Request Token matches the Consumer Key. + +</li> +<li> + The verification code received from the Consumer has been successfully verified. + +</li> +</ul><p> + +</p> +<p> + If successful, the Service Provider generates an Access Token and + Token Secret and returns them in the HTTP response body as defined + in <a class='info' href='#response_parameters'>Service Provider Response Parameters<span> (</span><span class='info'>Service Provider Response Parameters</span><span>)</span></a>. + The Access Token and Token Secret are stored by the Consumer and + used when signing Protected Resources requests. The response + contains the following parameters: + + </p> +<blockquote class="text"><dl> +<dt>oauth_token:</dt> +<dd> + The Access Token. + +</dd> +<dt>oauth_token_secret:</dt> +<dd> + The Token Secret. + +</dd> +<dt>Additional parameters:</dt> +<dd> + Any additional parameters, as defined by the Service Provider. + +</dd> +</dl></blockquote><p> + +</p> +<p> + If the request fails verification or is rejected for other reasons, + the Service Provider SHOULD respond with the appropriate response + code as defined in <a class='info' href='#http_codes'>HTTP Response Codes<span> (</span><span class='info'>HTTP Response Codes</span><span>)</span></a>. + The Service Provider MAY include some further details about why the + request was rejected in the HTTP response body as defined in + <a class='info' href='#response_parameters'>Service Provider Response Parameters<span> (</span><span class='info'>Service Provider Response Parameters</span><span>)</span></a>. + +</p> +<a name="anchor12"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.7"></a><h3>7. +Accessing Protected Resources</h3> + +<p> + After successfully receiving the Access Token and Token Secret, the + Consumer is able to access the Protected Resources on behalf of the + User. The request MUST be signed per + <a class='info' href='#signing_process'>Signing Requests<span> (</span><span class='info'>Signing Requests</span><span>)</span></a>, and + contains the following parameters: + + </p> +<blockquote class="text"><dl> +<dt>oauth_consumer_key:</dt> +<dd> + The Consumer Key. + +</dd> +<dt>oauth_token:</dt> +<dd> + The Access Token. + +</dd> +<dt>oauth_signature_method:</dt> +<dd> + The signature method the Consumer used to sign the request. + +</dd> +<dt>oauth_signature:</dt> +<dd> + The signature as defined in + <a class='info' href='#signing_process'>Signing Requests<span> (</span><span class='info'>Signing Requests</span><span>)</span></a>. + +</dd> +<dt>oauth_timestamp:</dt> +<dd> + As defined in <a class='info' href='#nonce'>Nonce and Timestamp<span> (</span><span class='info'>Nonce and Timestamp</span><span>)</span></a>. + +</dd> +<dt>oauth_nonce:</dt> +<dd> + As defined in <a class='info' href='#nonce'>Nonce and Timestamp<span> (</span><span class='info'>Nonce and Timestamp</span><span>)</span></a>. + +</dd> +<dt>oauth_version:</dt> +<dd> + OPTIONAL. If present, value MUST be <tt>1.0</tt>. Service Providers + MUST assume the protocol version to be <tt>1.0</tt> if this parameter + is not present. Service Providers' response to non-<tt>1.0</tt> value + is left undefined. + +</dd> +<dt>Additional parameters:</dt> +<dd> + Any additional parameters, as defined by the Service Provider. + +</dd> +</dl></blockquote><p> + +</p> +<a name="nonce"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.8"></a><h3>8. +Nonce and Timestamp</h3> + +<p> + Unless otherwise specified by the Service Provider, the timestamp is + expressed in the number of seconds since January 1, 1970 00:00:00 GMT. + The timestamp value MUST be a positive integer and MUST be equal or + greater than the timestamp used in previous requests. + +</p> +<p> + The Consumer SHALL then generate a Nonce value that is unique for all + requests with that timestamp. A nonce is a random string, uniquely + generated for each request. The nonce allows the Service Provider to + verify that a request has never been made before and helps prevent + replay attacks when requests are made over a non-secure channel + (such as HTTP). + +</p> +<a name="signing_process"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.9"></a><h3>9. +Signing Requests</h3> + +<p> + All Token requests and Protected Resources requests MUST be + signed by the Consumer and verified by the Service Provider. + The purpose of signing requests is to prevent unauthorized parties + from using the Consumer Key and Tokens when making Token requests or + Protected Resources requests. The signature process encodes + the Consumer Secret and Token Secret into a verifiable value which is + included with the request. + +</p> +<p> + OAuth does not mandate a particular signature method, as each + implementation can have its own unique requirements. The protocol + defines three signature methods: <tt>HMAC-SHA1</tt>, + <tt>RSA-SHA1</tt>, and + <tt>PLAINTEXT</tt>, but Service Providers + are free to implement and document their own methods. + Recommending any particular method is beyond the scope of this specification. + +</p> +<p> + The Consumer declares a signature method in the <tt>oauth_signature_method</tt> + parameter, generates a signature, and stores it in the <tt>oauth_signature</tt> + parameter. The Service Provider verifies the signature as specified in + each method. When verifying a Consumer signature, the Service Provider + SHOULD check the request nonce to ensure it has not been used in a + previous Consumer request. + +</p> +<p> + The signature process MUST NOT change the request parameter names or + values, with the exception of the <tt>oauth_signature</tt> parameter. + +</p> +<a name="anchor13"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.9.1"></a><h3>9.1. +Signature Base String</h3> + +<p> + The Signature Base String is a consistent reproducible concatenation + of the request elements into a single string. The string is used as an + input in hashing or signing algorithms. The <tt>HMAC-SHA1</tt> signature + method provides both a standard and an example of using the Signature + Base String with a signing algorithm to generate signatures. All + the request parameters MUST be encoded as described in + <a class='info' href='#encoding_parameters'>Parameter Encoding<span> (</span><span class='info'>Parameter Encoding</span><span>)</span></a> prior to + constructing the Signature Base String. + +</p> +<a name="sig_norm_param"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.9.1.1"></a><h3>9.1.1. +Normalize Request Parameters</h3> + +<p> + The request parameters are collected, sorted and concatenated into + a normalized string: + + </p> +<ul class="text"> +<li> + Parameters in the <a class='info' href='#auth_header_authorization'>OAuth HTTP Authorization header<span> (</span><span class='info'>Authorization Header</span><span>)</span></a> excluding the <tt>realm</tt> + parameter. + +</li> +<li> + Parameters in the HTTP POST request body (with a + <tt>content-type</tt> of + <tt>application/x-www-form-urlencoded</tt>). + +</li> +<li> + HTTP GET parameters added to the URLs in the query part (as defined by + <a class='info' href='#RFC3986'>[RFC3986]<span> (</span><span class='info'>Berners-Lee, T., “Uniform Resource Identifiers (URI): Generic Syntax,” .</span><span>)</span></a> section 3). + +</li> +</ul><p> + +</p> +<p> + The <tt>oauth_signature</tt> parameter MUST be + excluded. + +</p> +<p> + The parameters are normalized into a single string as follows: + + </p> +<ol class="text"> +<li> + Parameters are sorted by name, using lexicographical byte value + ordering. If two or more parameters share the same name, they + are sorted by their value. For example: + + <div style='display: table; width: 0; margin-left: 3em; margin-right: auto'><pre> + a=1, c=hi%20there, f=25, f=50, f=a, z=p, z=t +</pre></div> + +</li> +<li> + Parameters are concatenated in their sorted order into a single + string. For each parameter, the name is separated from the + corresponding value by an '=' character (ASCII code 61), even + if the value is empty. Each name-value pair is separated by an + '&' character (ASCII code 38). For example: + + <div style='display: table; width: 0; margin-left: 3em; margin-right: auto'><pre> + a=1&c=hi%20there&f=25&f=50&f=a&z=p&z=t +</pre></div> + +</li> +</ol><p> + +</p> +<a name="sig_url"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.9.1.2"></a><h3>9.1.2. +Construct Request URL</h3> + +<p> + The Signature Base String includes the request absolute URL, tying + the signature to a specific endpoint. The URL used in the Signature + Base String MUST include the scheme, authority, and path, and MUST + exclude the query and fragment as defined by <a class='info' href='#RFC3986'>[RFC3986]<span> (</span><span class='info'>Berners-Lee, T., “Uniform Resource Identifiers (URI): Generic Syntax,” .</span><span>)</span></a> + section 3. + +</p> +<p> + If the absolute request URL is not available to the Service Provider + (it is always available to the Consumer), it can be constructed by + combining the scheme being used, the HTTP <tt>Host</tt> + header, and the relative HTTP request URL. If the + <tt>Host</tt> header is not available, the Service + Provider SHOULD use the host name communicated to the Consumer in the + documentation or other means. + +</p> +<p> + The Service Provider SHOULD document the form of URL used in the + Signature Base String to avoid ambiguity due to URL normalization. + Unless specified, URL scheme and authority MUST be lowercase and + include the port number; <tt>http</tt> default + port 80 and <tt>https</tt> default port 443 MUST + be excluded. + +</p> +<p> + For example, the request: + + </p> +<div style='display: table; width: 0; margin-left: 3em; margin-right: auto'><pre> + HTTP://Example.com:80/resource?id=123 +</pre></div><p> + + + Is included in the Signature Base String as: + </p> +<div style='display: table; width: 0; margin-left: 3em; margin-right: auto'><pre> + http://example.com/resource +</pre></div><p> + + +</p> +<a name="anchor14"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.9.1.3"></a><h3>9.1.3. +Concatenate Request Elements</h3> + +<p> + The following items MUST be concatenated in order into a single + string. Each item is <a class='info' href='#encoding_parameters'>encoded<span> (</span><span class='info'>Parameter Encoding</span><span>)</span></a> + and separated by an '&' character (ASCII code 38), even if empty. + + </p> +<ol class="text"> +<li> + The HTTP request method used to send the request. Value MUST be + uppercase, for example: <tt>HEAD</tt>, <tt> + GET + </tt>, <tt>POST</tt>, etc. + +</li> +<li> + The request URL from <a class='info' href='#sig_url'>Section 9.1.2<span> (</span><span class='info'>Construct Request URL</span><span>)</span></a>. + +</li> +<li> + The normalized request parameters string from <a class='info' href='#sig_norm_param'>Section 9.1.1<span> (</span><span class='info'>Normalize Request Parameters</span><span>)</span></a>. + +</li> +</ol><p> + +</p> +<p> + See Signature Base String example in <a class='info' href='#sig_base_example'>Appendix A.5.1<span> (</span><span class='info'>Generating Signature Base String</span><span>)</span></a>. + +</p> +<a name="anchor15"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.9.2"></a><h3>9.2. +HMAC-SHA1</h3> + +<p> + The <tt>HMAC-SHA1</tt> signature method uses the HMAC-SHA1 signature + algorithm as defined in <a class='info' href='#RFC2104'>[RFC2104]<span> (</span><span class='info'>Krawczyk, H., Bellare, M., and R. Canetti, “HMAC: Keyed-Hashing for Message Authentication,” .</span><span>)</span></a> where the Signature + Base String is the <tt>text</tt> and the + <tt>key</tt> is the concatenated values + (each first encoded per <a class='info' href='#encoding_parameters'>Parameter Encoding<span> (</span><span class='info'>Parameter Encoding</span><span>)</span></a>) + of the Consumer Secret and Token Secret, separated by an '&' + character (ASCII code 38) even if empty. + +</p> +<a name="anchor16"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.9.2.1"></a><h3>9.2.1. +Generating Signature</h3> + +<p> + <tt>oauth_signature</tt> is set + to the calculated <tt>digest</tt> octet string, first base64-encoded per + <a class='info' href='#RFC2045'>[RFC2045]<span> (</span><span class='info'>Freed, N. and N. Borenstein, “Multipurpose Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies,” .</span><span>)</span></a> section 6.8, then URL-encoded per + <a class='info' href='#encoding_parameters'>Parameter Encoding<span> (</span><span class='info'>Parameter Encoding</span><span>)</span></a>. + +</p> +<a name="anchor17"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.9.2.2"></a><h3>9.2.2. +Verifying Signature</h3> + +<p> + The Service Provider verifies the request by generating a new request + signature octet string, and comparing it to the signature provided by the Consumer, + first URL-decoded per <a class='info' href='#encoding_parameters'>Parameter Encoding<span> (</span><span class='info'>Parameter Encoding</span><span>)</span></a>, + then base64-decoded per <a class='info' href='#RFC2045'>[RFC2045]<span> (</span><span class='info'>Freed, N. and N. Borenstein, “Multipurpose Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies,” .</span><span>)</span></a> section 6.8. + The signature is generated using the request parameters as provided + by the Consumer, and the Consumer Secret and Token Secret as stored + by the Service Provider. + +</p> +<a name="anchor18"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.9.3"></a><h3>9.3. +RSA-SHA1</h3> + +<p> + The <tt>RSA-SHA1</tt> signature method uses the + RSASSA-PKCS1-v1_5 signature algorithm as defined in + <a class='info' href='#RFC3447'>[RFC3447]<span> (</span><span class='info'>Jonsson, J. and B. Kaliski, “Public-Key Cryptography Standards (PKCS) #1: RSA Cryptography; Specifications Version 2.1,” .</span><span>)</span></a> section 8.2 (more simply known as PKCS#1), + using SHA-1 as the hash function for EMSA-PKCS1-v1_5. It is assumed + that the Consumer has provided its RSA public key in a verified way + to the Service Provider, in a manner which is beyond the scope of + this specification. + +</p> +<a name="anchor19"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.9.3.1"></a><h3>9.3.1. +Generating Signature</h3> + +<p> + The Signature Base String is signed using the Consumer's RSA private + key per <a class='info' href='#RFC3447'>[RFC3447]<span> (</span><span class='info'>Jonsson, J. and B. Kaliski, “Public-Key Cryptography Standards (PKCS) #1: RSA Cryptography; Specifications Version 2.1,” .</span><span>)</span></a> section 8.2.1, where <tt>K</tt> is the + Consumer's RSA private key, <tt>M</tt> the Signature Base String, and <tt>S</tt> is + the result signature octet string: + + </p> +<div style='display: table; width: 0; margin-left: 3em; margin-right: auto'><pre> + S = RSASSA-PKCS1-V1_5-SIGN (K, M) +</pre></div><p> + + +</p> +<p> + <tt>oauth_signature</tt> is set to <tt>S</tt>, first base64-encoded per + <a class='info' href='#RFC2045'>[RFC2045]<span> (</span><span class='info'>Freed, N. and N. Borenstein, “Multipurpose Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies,” .</span><span>)</span></a> section 6.8, then URL-encoded per + <a class='info' href='#encoding_parameters'>Parameter Encoding<span> (</span><span class='info'>Parameter Encoding</span><span>)</span></a>. + +</p> +<a name="anchor20"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.9.3.2"></a><h3>9.3.2. +Verifying Signature</h3> + +<p> + The Service Provider verifies the signature per <a class='info' href='#RFC3447'>[RFC3447]<span> (</span><span class='info'>Jonsson, J. and B. Kaliski, “Public-Key Cryptography Standards (PKCS) #1: RSA Cryptography; Specifications Version 2.1,” .</span><span>)</span></a> + section 8.2.2, where <tt> + (n, e) + </tt> is the Consumer's RSA public key, <tt>M</tt> + is the Signature Base String, and <tt>S</tt> is the octet string + representation of the <tt>oauth_signature</tt> value: + + </p> +<div style='display: table; width: 0; margin-left: 3em; margin-right: auto'><pre> + RSASSA-PKCS1-V1_5-VERIFY ((n, e), M, S) +</pre></div><p> + + +</p> +<a name="anchor21"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.9.4"></a><h3>9.4. +PLAINTEXT</h3> + +<p> + The <tt> + PLAINTEXT + </tt> method does not provide any security protection and + SHOULD only be used over a secure channel such as HTTPS. It does not + use the Signature Base String. + +</p> +<a name="anchor22"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.9.4.1"></a><h3>9.4.1. +Generating Signature</h3> + +<p> + <tt>oauth_signature</tt> is set to the concatenated encoded values of the + Consumer Secret and Token Secret, separated by a '&' character (ASCII + code 38), even if either secret is empty. The result MUST be encoded again. + +</p> +<p> + These examples show the value of <tt>oauth_signature</tt> + for Consumer Secret <tt>djr9rjt0jd78jf88</tt> and + 3 different Token Secrets: + + </p> +<blockquote class="text"><dl> +<dt>jjd999tj88uiths3:</dt> +<dd> + <tt>oauth_signature</tt>=<tt>djr9rjt0jd78jf88%26jjd999tj88uiths3</tt> + +</dd> +<dt>jjd99$tj88uiths3:</dt> +<dd> + <tt>oauth_signature</tt>=<tt>djr9rjt0jd78jf88%26jjd99%2524tj88uiths3</tt> + +</dd> +<dt>Empty:</dt> +<dd> + <tt>oauth_signature</tt>=<tt>djr9rjt0jd78jf88%26</tt> + +</dd> +</dl></blockquote><p> + +</p> +<a name="anchor23"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.9.4.2"></a><h3>9.4.2. +Verifying Signature</h3> + +<p> + The Service Provider verifies the request by breaking the signature + value into the Consumer Secret and Token Secret, and ensures they + match the secrets stored locally. + +</p> +<a name="http_codes"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.10"></a><h3>10. +HTTP Response Codes</h3> + +<p> + This section applies only to the Request Token and Access Token + requests. In general, the Service Provider SHOULD use the + response codes defined in <a class='info' href='#RFC2616'>[RFC2616]<span> (</span><span class='info'>Fielding, R., Gettys, J., Mogul, J., Frystyk, H., Masinter, L., Leach, P., and T. Berners-Lee, “Hypertext Transfer Protocol -- HTTP/1.1,” .</span><span>)</span></a> Section 10. When + the Service Provider rejects a Consumer request, it SHOULD respond with + HTTP 400 Bad Request or HTTP 401 Unauthorized. + + </p> +<ul class="text"> +<li> + HTTP 400 Bad Request + +<ul class="text"> +<li> + Unsupported parameter + +</li> +<li> + Unsupported signature method + +</li> +<li> + Missing required parameter + +</li> +<li> + Duplicated OAuth Protocol Parameter + +</li> +</ul> + +</li> +<li> + HTTP 401 Unauthorized + +<ul class="text"> +<li> + Invalid Consumer Key + +</li> +<li> + Invalid / expired Token + +</li> +<li> + Invalid signature + +</li> +<li> + Invalid / used nonce + +</li> +</ul> + +</li> +</ul><p> + +</p> +<a name="anchor24"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.11"></a><h3>11. +Security Considerations</h3> + +<a name="anchor25"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.11.1"></a><h3>11.1. +Credentials and Token Exchange</h3> + +<p> + The OAuth specification does not describe any mechanism for protecting + Tokens and secrets from eavesdroppers when they are transmitted from + the Service Provider to the Consumer in <a class='info' href='#request_grant'>Section 6.1.2<span> (</span><span class='info'>Service Provider Issues an Unauthorized Request Token</span><span>)</span></a> + and <a class='info' href='#access_grant'>Section 6.3.2<span> (</span><span class='info'>Service Provider Grants an Access Token</span><span>)</span></a>. Service Providers should ensure + that these transmissions are protected using transport-layer mechanisms + such as TLS or SSL. + +</p> +<a name="anchor26"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.11.2"></a><h3>11.2. +PLAINTEXT Signature Method</h3> + +<p> + When used with <tt>PLAINTEXT</tt> signatures, the + OAuth protocol makes no attempts to protect User credentials from + eavesdroppers or man-in-the-middle attacks. + The <tt>PLAINTEXT</tt> signature algorithm is only + intended to be used in conjunction with a transport-layer security + mechanism such as TLS or SSL which does provide such protection. + If transport-layer protection is unavailable, the + <tt>PLAINTEXT</tt> signature method should not be + used. + +</p> +<a name="anchor27"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.11.3"></a><h3>11.3. +Confidentiality of Requests</h3> + +<p> + While OAuth provides a mechanism for verifying the integrity of + requests, it provides no guarantee of request confidentiality. + Unless further precautions are taken, eavesdroppers will have full + access to request content. Service Providers should carefully + consider the kinds of data likely to be sent as part of such requests, + and should employ transport-layer security mechanisms to protect + sensitive resources. + +</p> +<a name="anchor28"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.11.4"></a><h3>11.4. +Spoofing by Counterfeit Servers</h3> + +<p> + OAuth makes no attempt to verify the authenticity of the Service + Provider. A hostile party could take advantage of this by intercepting + the Consumer's requests and returning misleading or otherwise incorrect + responses. Service providers should consider such attacks when + developing services based on OAuth, and should require transport-layer + security for any requests where the authenticity of the Service + Provider or of request responses is an issue. + +</p> +<a name="anchor29"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.11.5"></a><h3>11.5. +Proxying and Caching of Authenticated Content</h3> + +<p> + The <a class='info' href='#auth_header'>HTTP Authorization scheme<span> (</span><span class='info'>OAuth HTTP Authorization Scheme</span><span>)</span></a> is + optional. However, <a class='info' href='#RFC2616'>[RFC2616]<span> (</span><span class='info'>Fielding, R., Gettys, J., Mogul, J., Frystyk, H., Masinter, L., Leach, P., and T. Berners-Lee, “Hypertext Transfer Protocol -- HTTP/1.1,” .</span><span>)</span></a> relies on the + <tt>Authorization</tt> and + <tt>WWW-Authenticate</tt> headers to distinguish + authenticated content so that it can be protected. Proxies and + caches, in particular, may fail to adequately protect requests not + using these headers. + +</p> +<p> + For example, private authenticated content may be stored in (and thus + retrievable from) publicly-accessible caches. Service Providers not + using the <a class='info' href='#auth_header'>HTTP Authorization scheme<span> (</span><span class='info'>OAuth HTTP Authorization Scheme</span><span>)</span></a> + should take care to use other mechanisms, such as the + <tt>Cache-Control</tt> header, to ensure that + authenticated content is protected. + +</p> +<a name="anchor30"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.11.6"></a><h3>11.6. +Plaintext Storage of Credentials</h3> + +<p> + The Consumer Secret and Token Secret function the same way passwords + do in traditional authentication systems. In order to compute the + signatures used in the non-<tt>PLAINTEXT</tt> + methods, the Service Provider must have access to these secrets in + plaintext form. This is in contrast, for example, to modern operating + systems, which store only a one-way hash of user credentials. + +</p> +<p> + If an attacker were to gain access to these secrets - or worse, to + the Service Provider's database of all such secrets - he or she would + be able to perform any action on behalf of any User. Accordingly, it + is critical that Service Providers protect these secrets from + unauthorized access. + +</p> +<a name="anchor31"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.11.7"></a><h3>11.7. +Secrecy of the Consumer Secret</h3> + +<p> + In many applications, the Consumer application will be under the + control of potentially untrusted parties. For example, if the + Consumer is a freely available desktop application, an attacker may + be able to download a copy for analysis. In such cases, attackers + will be able to recover the Consumer Secret used to authenticate the + Consumer to the Service Provider. + +</p> +<p> + Accordingly, Service Providers should not use the Consumer Secret + alone to verify the identity of the Consumer. Where possible, other + factors such as IP address should be used as well. + +</p> +<a name="anchor32"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.11.8"></a><h3>11.8. +Phishing Attacks</h3> + +<p> + Wide deployment of OAuth and similar protocols may cause + Users to become inured to the practice of being redirected to + websites where they are asked to enter their passwords. If Users are + not careful to verify the authenticity of these websites before + entering their credentials, it will be possible for attackers to + exploit this practice to steal Users' passwords. + +</p> +<p> + Service Providers should attempt to educate Users about the risks + phishing attacks pose, and should provide mechanisms that make it + easy for Users to confirm the authenticity of their sites. + +</p> +<a name="anchor33"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.11.9"></a><h3>11.9. +Scoping of Access Requests</h3> + +<p> + By itself, OAuth does not provide any method for scoping the access + rights granted to a Consumer. A Consumer either has access to + Protected Resources or it doesn't. Many applications will, however, + require greater granularity of access rights. For example, Service + Providers may wish to make it possible to grant access to some + Protected Resources but not others, or to grant only limited access + (such as read-only access) to those Protected Resources. + +</p> +<p> + When implementing OAuth, Service Providers should consider the types + of access Users may wish to grant Consumers, and should provide + mechanisms to do so. Service Providers should also take care to + ensure that Users understand the access they are granting, as well as + any risks that may be involved. + +</p> +<a name="anchor34"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.11.10"></a><h3>11.10. +Entropy of Secrets</h3> + +<p> + Unless a transport-layer security protocol is used, eavesdroppers will + have full access to OAuth requests and signatures, and will thus be + able to mount offline brute-force attacks to recover the Consumer's + credentials used. Service Providers should be careful to assign Token + Secrets and Consumer Secrets which are long enough - and random enough + - to resist such attacks for at least the length of time that the + secrets are valid. + +</p> +<p> + For example, if Token Secrets are valid for two weeks, Service + Providers should ensure that it is not possible to mount a brute force + attack that recovers the Token Secret in less than two weeks. Of + course, Service Providers are urged to err on the side of caution, + and use the longest secrets reasonable. + +</p> +<p> + It is equally important that the pseudo-random number generator (PRNG) + used to generate these secrets be of sufficiently high quality. Many + PRNG implementations generate number sequences that may appear to be + random, but which nevertheless exhibit patterns or other weaknesses + which make cryptanalysis or brute force attacks easier. Implementors + should be careful to use cryptographically secure PRNGs to avoid these + problems. + +</p> +<a name="anchor35"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.11.11"></a><h3>11.11. +Denial of Service / Resource Exhaustion Attacks</h3> + +<p> + The OAuth protocol has a number of features which may make resource + exhaustion attacks against Service Providers possible. For example, + if a Service Provider includes a nontrivial amount of entropy in Token + Secrets as recommended above, then an attacker may be able to exhaust + the Service Provider's entropy pool very quickly by repeatedly + obtaining Request Tokens from the Service Provider. + +</p> +<p> + Similarly, OAuth requires Service Providers to track used nonces. If + an attacker is able to use many nonces quickly, the resources required + to track them may exhaust available capacity. And again, OAuth can + require Service Providers to perform potentially expensive computations + in order to verify the signature on incoming requests. An attacker may + exploit this to perform a denial of service attack by sending a large + number of invalid requests to the Service Provider. + +</p> +<p> + Resource Exhaustion attacks are by no means specific to OAuth. However, + OAuth implementors should be careful to consider the additional + avenues of attack that OAuth exposes, and design their implementations + accordingly. For example, entropy starvation typically results in + either a complete denial of service while the system waits for new + entropy or else in weak (easily guessable) secrets. When implementing + OAuth, Service Providers should consider which of these presents a + more serious risk for their application and design accordingly. + +</p> +<a name="anchor36"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.11.12"></a><h3>11.12. +Cryptographic Attacks</h3> + +<p> + SHA-1, the hash algorithm used in <tt>HMAC-SHA1</tt> + signatures, has been <a class='info' href='#SHA1'>shown<span> (</span><span class='info'>De Canniere, C. and C. Rechberger, “Finding SHA-1 Characteristics: General Results and Applications,” .</span><span>)</span></a> [SHA1] to have a number + of cryptographic weaknesses that significantly reduce its resistance to + collision attacks. Practically speaking, these weaknesses are difficult + to exploit, and by themselves do not pose a significant risk to users + of OAuth. They may, however, make more efficient attacks possible, and + NIST has <a class='info' href='#NIST'>announced<span> (</span><span class='info'>National Institute of Standards and Technolog, NIST., “NIST Brief Comments on Recent Cryptanalytic Attacks on Secure Hashing Functions and the Continued Security Provided by SHA-1,” .</span><span>)</span></a> [NIST] that it will phase out + use of SHA-1 by 2010. Service Providers should take this into account + when considering whether SHA-1 provides an adequate level of security + for their applications. + +</p> +<a name="anchor37"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.11.13"></a><h3>11.13. +Signature Base String Compatibility</h3> + +<p> + The Signature Base String has been designed to support the signature + methods defined in this specification. When designing additional + signature methods, the Signature Base String should be evaluated to + ensure compatibility with the algorithms used. + +</p> +<p> + The Signature Base String cannot guarantee the order in which parameters + are sent. If parameter ordering is important and affects the result of a + request, the Signature Base String will not protect against request + manipulation. + +</p> +<a name="anchor38"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.11.14"></a><h3>11.14. +Cross-Site Request Forgery (CSRF)</h3> + +<p> + Cross-Site Request Forgery (CSRF) is a web-based attack whereby HTTP requests + are transmitted from a user that the website trusts or has authenticated. + CSRF attacks on OAuth approvals can allow an attacker to obtain authorization to + OAuth Protected Resources without the consent of the User. Service Providers + SHOULD strongly consider best practices in CSRF prevention at all OAuth endpoints. + +</p> +<p> + CSRF attacks on OAuth callback URLs hosted by Consumers are also possible. + Consumers should prevent CSRF attacks on OAuth callback URLs by verifying that + the User at the Consumer site intended to complete the OAuth negotiation with the + Service Provider. + +</p> +<a name="anchor39"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.11.15"></a><h3>11.15. +User Interface Redress</h3> + +<p> + Service Providers should protect the authorization process against UI Redress attacks + (also known as "clickjacking"). As of the time of this writing, no complete defenses + against UI redress are available. Service Providers can mitigate the risk of UI + redress attacks through the following techniques: + + </p> +<ul class="text"> +<li>Javascript frame busting. +</li> +<li>Javascript frame busting, and requiring that browsers have javascript enabled on the authorization page. +</li> +<li>Browser-specific anti-framing techniques. +</li> +<li>Requiring password reentry before issuing OAuth tokens. +</li> +</ul><p> + +</p> +<a name="anchor40"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.11.16"></a><h3>11.16. +Automatic Processing of Repeat Authorizations</h3> + +<p> + Service Providers may wish to automatically process authorization requests + (<a class='info' href='#auth_step2'>Section 6.2<span> (</span><span class='info'>Obtaining User Authorization</span><span>)</span></a>) from Consumers which have been previously + authorized by the user. When the User is redirected to the Service Provider + to grant access, the Service Provider detects that the User has already granted + access to that particular Consumer. Instead of prompting the User for approval, + the Service Provider automatically redirects the User back to the Provider. + +</p> +<p> + If the Consumer Secret is compromised, automatic processing creates additional + security risks. An attacker can use the stolen Consumer Key and Secret to redirect + the User to the Service Provider with an authorization request. The Service Provider + will then grant access to the User's data without the User's explicit approval, or + even awareness of an attack. If no automatic approval is implemented, an attacker + must use social engineering to convince the User to approve access. + +</p> +<p> + Service Providers can mitigate the risks associated with automatic processing by + limiting the scope of Access Tokens obtained through automated approvals. Access + Tokens obtained through explicit User consent can remain unaffected. Consumers can + mitigate the risks associated with automatic processing by protecting their Consumer + Secret. + +</p> +<a name="anchor41"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.A"></a><h3>Appendix A. +Appendix A - Protocol Example</h3> + +<p> + In this example, the Service Provider photos.example.net is a photo + sharing website, and the Consumer printer.example.com is a photo + printing website. Jane, the User, would like printer.example.com to + print the private photo <tt> + vacation.jpg + </tt> stored at photos.example.net. + +</p> +<p> + When Jane signs-into photos.example.net using her username and + password, she can access the photo by going to the URL + <tt>http://photos.example.net/photo?file=vacation.jpg</tt>. Other Users + cannot access that photo, and Jane does not want to share her + username and password with printer.example.com. + +</p> +<p> + The requests in this example use the URL query method when sending + parameters. This is done to simplify the example and should not be + taken as an endorsement of one method over the others. + +</p> +<a name="anchor42"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.A.1"></a><h3>Appendix A.1. +Documentation and Registration</h3> + +<p> + The Service Provider documentation explains how to register for a + Consumer Key and Consumer Secret, and declares the following URLs: + + </p> +<blockquote class="text"><dl> +<dt>Request Token URL:</dt> +<dd> + https://photos.example.net/request_token, using HTTP POST + +</dd> +<dt>User Authorization URL:</dt> +<dd> + http://photos.example.net/authorize, using HTTP GET + +</dd> +<dt>Access Token URL:</dt> +<dd> + https://photos.example.net/access_token, using HTTP POST + +</dd> +<dt>Photo (Protected Resource) URL:</dt> +<dd> + http://photos.example.net/photo with required parameter + <tt>file</tt> and optional parameter <tt>size</tt> + +</dd> +</dl></blockquote><p> + +</p> +<p> + The Service Provider declares support for the <tt> + HMAC-SHA1 + </tt> signature + method for all requests, and <tt>PLAINTEXT</tt> only for secure (HTTPS) + requests. + +</p> +<p> + The Consumer printer.example.com already established a Consumer Key + and Consumer Secret with photos.example.net and advertizes its + printing services for photos stored on photos.example.net. The + Consumer registration is: + + </p> +<blockquote class="text"><dl> +<dt>Consumer Key:</dt> +<dd> + <tt> + dpf43f3p2l4k3l03 + </tt> + +</dd> +<dt>Consumer Secret:</dt> +<dd> + <tt>kd94hf93k423kf44</tt> + +</dd> +</dl></blockquote><p> + +</p> +<a name="anchor43"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.A.2"></a><h3>Appendix A.2. +Obtaining a Request Token</h3> + +<p> + After Jane informs printer.example.com that she would like to print + her vacation photo stored at photos.example.net, the printer website + tries to access the photo and receives HTTP 401 Unauthorized + indicating it is private. The Service Provider includes the following + header with the response: + + </p> +<div style='display: table; width: 0; margin-left: 3em; margin-right: auto'><pre> + WWW-Authenticate: OAuth realm="http://photos.example.net/" +</pre></div><p> + + +</p> +<p> + The Consumer sends the following HTTP POST request to the Service + Provider: + + </p> +<div style='display: table; width: 0; margin-left: 3em; margin-right: auto'><pre> + https://photos.example.net/request_token?oauth_consumer_key=dpf43f3p2l4k3l03&oauth_signature_method=PLAINTEXT&oauth_signature=kd94hf93k423kf44%26&oauth_timestamp=1191242090&oauth_nonce=hsu94j3884jdopsl&oauth_version=1.0&oauth_callback=http%3A%2F%2Fprinter.example.com%2Frequest_token_ready +</pre></div><p> + + +</p> +<p> + The Service Provider checks the signature and replies with an + unauthorized Request Token in the body of the HTTP response: + + </p> +<div style='display: table; width: 0; margin-left: 3em; margin-right: auto'><pre> + oauth_token=hh5s93j4hdidpola&oauth_token_secret=hdhd0244k9j7ao03 +</pre></div><p> + + +</p> +<a name="anchor44"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.A.3"></a><h3>Appendix A.3. +Requesting User Authorization</h3> + +<p> + The Consumer redirects Jane's browser to the Service Provider + User Authorization URL to obtain Jane's approval for accessing + her private photos. + + </p> +<div style='display: table; width: 0; margin-left: 3em; margin-right: auto'><pre> + http://photos.example.net/authorize?oauth_token=hh5s93j4hdidpola +</pre></div><p> + + +</p> +<p> + The Service Provider asks Jane to sign-in using her username and + password and, if successful, asks her if she approves granting + printer.example.com access to her private photos. If Jane approves + the request, the Service Provider generates a verification code and + redirects her back to the Consumer's callback URL: + + </p> +<div style='display: table; width: 0; margin-left: 3em; margin-right: auto'><pre> + http://printer.example.com/request_token_ready?oauth_token=hh5s93j4hdidpola&oauth_verifier=hfdp7dh39dks9884 +</pre></div><p> + + +</p> +<a name="anchor45"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.A.4"></a><h3>Appendix A.4. +Obtaining an Access Token</h3> + +<p> + Now that the Consumer knows Jane approved the Request Token, it + asks the Service Provider to exchange it for an Access Token: + + </p> +<div style='display: table; width: 0; margin-left: 3em; margin-right: auto'><pre> + https://photos.example.net/access_token?oauth_consumer_key=dpf43f3p2l4k3l03&oauth_token=hh5s93j4hdidpola&oauth_signature_method=PLAINTEXT&oauth_signature=kd94hf93k423kf44%26hdhd0244k9j7ao03&oauth_timestamp=1191242092&oauth_nonce=dji430splmx33448&oauth_version=1.0&oauth_verifier=hfdp7dh39dks9884 +</pre></div><p> + + +</p> +<p> + The Service Provider checks the signature and the verification code and replies with an + Access Token in the body of the HTTP response: + + </p> +<div style='display: table; width: 0; margin-left: 3em; margin-right: auto'><pre> + oauth_token=nnch734d00sl2jdk&oauth_token_secret=pfkkdhi9sl3r4s00 +</pre></div><p> + + +</p> +<a name="anchor46"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.A.5"></a><h3>Appendix A.5. +Accessing Protected Resources</h3> + +<p> + The Consumer is now ready to request the private photo. Since the + photo URL is not secure (HTTP), it must use <tt>HMAC-SHA1</tt>. + +</p> +<a name="sig_base_example"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.A.5.1"></a><h3>Appendix A.5.1. +Generating Signature Base String</h3> + +<p> + To generate the signature, it first needs to generate the Signature + Base String. The request contains the following parameters + (<tt>oauth_signature</tt> excluded) which are ordered and concatenated into + a normalized string: + + </p> +<blockquote class="text"><dl> +<dt>oauth_consumer_key:</dt> +<dd> + <tt>dpf43f3p2l4k3l03</tt> + +</dd> +<dt>oauth_token:</dt> +<dd> + <tt>nnch734d00sl2jdk</tt> + +</dd> +<dt>oauth_signature_method:</dt> +<dd> + <tt>HMAC-SHA1</tt> + +</dd> +<dt>oauth_timestamp:</dt> +<dd> + <tt>1191242096</tt> + +</dd> +<dt>oauth_nonce:</dt> +<dd> + <tt>kllo9940pd9333jh</tt> + +</dd> +<dt>oauth_version:</dt> +<dd> + <tt>1.0</tt> + +</dd> +<dt>file:</dt> +<dd> + <tt>vacation.jpg</tt> + +</dd> +<dt>size:</dt> +<dd> + <tt>original</tt> + +</dd> +</dl></blockquote><p> + +</p> +<p> + The following inputs are used to generate the Signature Base String: + + </p> +<ol class="text"> +<li> + <tt>GET</tt> + +</li> +<li> + <tt>http://photos.example.net/photos</tt> + +</li> +<li> + <tt>file=vacation.jpg&oauth_consumer_key=dpf43f3p2l4k3l03&oauth_nonce=kllo9940pd9333jh&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1191242096&oauth_token=nnch734d00sl2jdk&oauth_version=1.0&size=original</tt> + +</li> +</ol><p> + +</p> +<p> + The Signature Base String is: + + </p> +<div style='display: table; width: 0; margin-left: 3em; margin-right: auto'><pre> + GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26oauth_version%3D1.0%26size%3Doriginal +</pre></div><p> + + +</p> +<a name="anchor47"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.A.5.2"></a><h3>Appendix A.5.2. +Calculating Signature Value</h3> + +<p> + HMAC-SHA1 produces the following <tt>digest</tt> value as a base64-encoded + string (using the Signature Base String as <tt>text</tt> and + <tt> + kd94hf93k423kf44&pfkkdhi9sl3r4s00 + </tt> as <tt>key</tt>): + + </p> +<div style='display: table; width: 0; margin-left: 3em; margin-right: auto'><pre> + tR3+Ty81lMeYAr/Fid0kMTYa/WM= +</pre></div><p> + + +</p> +<a name="anchor48"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<a name="rfc.section.A.5.3"></a><h3>Appendix A.5.3. +Requesting Protected Resource</h3> + +<p> + All together, the Consumer request for the photo is: + + </p> +<div style='display: table; width: 0; margin-left: 3em; margin-right: auto'><pre> + http://photos.example.net/photos?file=vacation.jpg&size=original + + Authorization: OAuth realm="http://photos.example.net/", + oauth_consumer_key="dpf43f3p2l4k3l03", + oauth_token="nnch734d00sl2jdk", + oauth_signature_method="HMAC-SHA1", + oauth_signature="tR3%2BTy81lMeYAr%2FFid0kMTYa%2FWM%3D", + oauth_timestamp="1191242096", + oauth_nonce="kllo9940pd9333jh", + oauth_version="1.0" +</pre></div><p> + + +</p> +<p> + And if using query parameters: + + </p> +<div style='display: table; width: 0; margin-left: 3em; margin-right: auto'><pre> + http://photos.example.net/photos?file=vacation.jpg&size=original&oauth_consumer_key=dpf43f3p2l4k3l03&oauth_token=nnch734d00sl2jdk&oauth_signature_method=HMAC-SHA1&oauth_signature=tR3%2BTy81lMeYAr%2FFid0kMTYa%2FWM%3D&oauth_timestamp=1191242096&oauth_nonce=kllo9940pd9333jh&oauth_version=1.0 +</pre></div><p> + + +</p> +<p> + photos.example.net checks the signature and responds with the + requested photo. + +</p> +<a name="rfc.references1"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<h3>12. References</h3> +<table width="99%" border="0"> +<tr><td class="author-text" valign="top"><a name="NIST">[NIST]</a></td> +<td class="author-text">National Institute of Standards and Technolog, NIST., “<a href="http://csrc.nist.gov/hash_standards_comments.pdf">NIST Brief Comments on Recent Cryptanalytic Attacks on Secure Hashing Functions and the Continued Security Provided by SHA-1</a>.”</td></tr> +<tr><td class="author-text" valign="top"><a name="RFC2045">[RFC2045]</a></td> +<td class="author-text">Freed, N. and N. Borenstein, “<a href="http://tools.ietf.org/html/rfc2045">Multipurpose Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies</a>,” RFC 2045.</td></tr> +<tr><td class="author-text" valign="top"><a name="RFC2104">[RFC2104]</a></td> +<td class="author-text">Krawczyk, H., Bellare, M., and R. Canetti, “<a href="http://tools.ietf.org/html/rfc2104">HMAC: Keyed-Hashing for Message Authentication</a>,” RFC 2104.</td></tr> +<tr><td class="author-text" valign="top"><a name="RFC2119">[RFC2119]</a></td> +<td class="author-text">Bradner, B., “<a href="http://tools.ietf.org/html/rfc2119">Key words for use in RFCs to Indicate Requirement Levels</a>,” RFC 2119.</td></tr> +<tr><td class="author-text" valign="top"><a name="RFC2606">[RFC2606]</a></td> +<td class="author-text">Eastlake, D. and A. Panitz, “<a href="http://tools.ietf.org/html/rfc2606">Reserved Top Level DNS Names</a>,” RFC 2606.</td></tr> +<tr><td class="author-text" valign="top"><a name="RFC2616">[RFC2616]</a></td> +<td class="author-text">Fielding, R., Gettys, J., Mogul, J., Frystyk, H., Masinter, L., Leach, P., and T. Berners-Lee, “<a href="http://tools.ietf.org/html/rfc2616">Hypertext Transfer Protocol -- HTTP/1.1</a>,” RFC 2616.</td></tr> +<tr><td class="author-text" valign="top"><a name="RFC2617">[RFC2617]</a></td> +<td class="author-text">Franks, J., Hallam-Baker, P., Hostetler, J., Lawrence, S., Leach, P., Luotonen, A., and L. Stewart, “<a href="http://tools.ietf.org/html/rfc2617">HTTP Authentication: Basic and Digest Access Authentication</a>,” RFC 2617.</td></tr> +<tr><td class="author-text" valign="top"><a name="RFC3447">[RFC3447]</a></td> +<td class="author-text">Jonsson, J. and B. Kaliski, “<a href="http://tools.ietf.org/html/rfc3447">Public-Key Cryptography Standards (PKCS) #1: RSA Cryptography; Specifications Version 2.1</a>,” RFC 3447.</td></tr> +<tr><td class="author-text" valign="top"><a name="RFC3629">[RFC3629]</a></td> +<td class="author-text">Yergeau, F., “<a href="http://tools.ietf.org/html/rfc3629">UTF-8, a transformation format of Unicode and ISO 10646</a>,” RFC 3629.</td></tr> +<tr><td class="author-text" valign="top"><a name="RFC3986">[RFC3986]</a></td> +<td class="author-text">Berners-Lee, T., “<a href="http://tools.ietf.org/html/rfc3986">Uniform Resource Identifiers (URI): Generic Syntax</a>,” RFC 3986.</td></tr> +<tr><td class="author-text" valign="top"><a name="SHA1">[SHA1]</a></td> +<td class="author-text">De Canniere, C. and C. Rechberger, “<a href="http://dx.doi.org/10.1007/11935230_1">Finding SHA-1 Characteristics: General Results and Applications</a>.”</td></tr> +</table> + +<a name="rfc.authors"></a><br /><hr /> +<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc"> TOC </a></td></tr></table> +<h3>Author's Address</h3> +<table width="99%" border="0" cellpadding="0" cellspacing="0"> +<tr><td class="author-text"> </td> +<td class="author-text">OAuth Core Workgroup</td></tr> +<tr><td class="author" align="right">Email: </td> +<td class="author-text"><a href="mailto:spec@oauth.net">spec@oauth.net</a></td></tr> +</table> +<script type="text/javascript"> +var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www."); +document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E")); +</script> + +<script type="text/javascript"> +try { +var pageTracker = _gat._getTracker("UA-2649949-1"); +pageTracker._trackPageview(); +} catch(err) {}</script> +</body></html> + diff --git a/samples/OAuthServiceProvider/App_Code/CustomOAuthTypeProvider.cs b/samples/OAuthServiceProvider/App_Code/CustomOAuthTypeProvider.cs index 9fdbf29..b08c8dc 100644 --- a/samples/OAuthServiceProvider/App_Code/CustomOAuthTypeProvider.cs +++ b/samples/OAuthServiceProvider/App_Code/CustomOAuthTypeProvider.cs @@ -15,7 +15,8 @@ public class CustomOAuthMessageFactory : OAuthServiceProviderMessageFactory { /// Initializes a new instance of the <see cref="CustomOAuthMessageFactory"/> class. /// </summary> /// <param name="tokenManager">The token manager instance to use.</param> - public CustomOAuthMessageFactory(IServiceProviderTokenManager tokenManager) : base(tokenManager) { + public CustomOAuthMessageFactory(IServiceProviderTokenManager tokenManager) + : base(tokenManager) { } public override IDirectedProtocolMessage GetNewRequestMessage(MessageReceivingEndpoint recipient, IDictionary<string, string> fields) { @@ -23,7 +24,7 @@ public class CustomOAuthMessageFactory : OAuthServiceProviderMessageFactory { // inject our own type here to replace the standard one if (message is UnauthorizedTokenRequest) { - message = new RequestScopedTokenMessage(recipient); + message = new RequestScopedTokenMessage(recipient, new Version(1, 0, 1)); // we're doing 1.0a here } return message; diff --git a/samples/OAuthServiceProvider/App_Code/DataClasses.dbml b/samples/OAuthServiceProvider/App_Code/DataClasses.dbml index 0b54d0d..6385d14 100644 --- a/samples/OAuthServiceProvider/App_Code/DataClasses.dbml +++ b/samples/OAuthServiceProvider/App_Code/DataClasses.dbml @@ -38,6 +38,7 @@ <Column Name="ConsumerId" Type="System.Int32" DbType="Int NOT NULL" CanBeNull="false" /> <Column Name="UserId" Type="System.Int32" DbType="Int" CanBeNull="true" /> <Column Name="Scope" Type="System.String" DbType="nvarchar(MAX)" CanBeNull="false" /> + <Column Member="RequestTokenVerifier" Type="System.String" CanBeNull="true" /> <Association Name="OAuthConsumer_OAuthToken" Member="OAuthConsumer" ThisKey="ConsumerId" OtherKey="ConsumerId" Type="OAuthConsumer" IsForeignKey="true" DeleteRule="CASCADE" DeleteOnNull="true" /> <Association Name="User_OAuthToken" Member="User" ThisKey="UserId" OtherKey="UserId" Type="User" IsForeignKey="true" DeleteRule="CASCADE" /> </Type> diff --git a/samples/OAuthServiceProvider/App_Code/DataClasses.dbml.layout b/samples/OAuthServiceProvider/App_Code/DataClasses.dbml.layout index 1fc61cf..17a5432 100644 --- a/samples/OAuthServiceProvider/App_Code/DataClasses.dbml.layout +++ b/samples/OAuthServiceProvider/App_Code/DataClasses.dbml.layout @@ -20,10 +20,10 @@ <elementListCompartment Id="464308c4-d112-4448-b0c9-d9b82fb0ca4e" absoluteBounds="0.64, 3.71, 1.9700000000000002, 0.8262939453125" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" /> </nestedChildShapes> </classShape> - <classShape Id="895ebbc8-8352-4c04-9e53-b8e6c8302d36" absoluteBounds="3.5, 3.125, 2, 2.3478011067708326"> + <classShape Id="895ebbc8-8352-4c04-9e53-b8e6c8302d36" absoluteBounds="3.5, 3.125, 2, 2.5401025390624996"> <DataClassMoniker Name="/DataClassesDataContext/OAuthToken" /> <nestedChildShapes> - <elementListCompartment Id="403126d0-3d2a-4af4-b0b8-c489a830bbd4" absoluteBounds="3.515, 3.585, 1.9700000000000002, 1.7878011067708333" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" /> + <elementListCompartment Id="403126d0-3d2a-4af4-b0b8-c489a830bbd4" absoluteBounds="3.515, 3.585, 1.9700000000000002, 1.9801025390625" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" /> </nestedChildShapes> </classShape> <associationConnector edgePoints="[(2.625 : 1.31814697265625); (3.5 : 1.31814697265625)]" fixedFrom="NotFixed" fixedTo="NotFixed"> @@ -40,7 +40,7 @@ <classShapeMoniker Id="895ebbc8-8352-4c04-9e53-b8e6c8302d36" /> </nodes> </associationConnector> - <associationConnector edgePoints="[(0.53125 : 2.27089680989583); (0.53125 : 5.08579752604167); (3.5 : 5.08579752604167)]" fixedFrom="Algorithm" fixedTo="Algorithm"> + <associationConnector edgePoints="[(0.53125 : 2.27089680989583); (0.53125 : 5.1819482421875); (3.5 : 5.1819482421875)]" fixedFrom="Algorithm" fixedTo="Algorithm"> <AssociationMoniker Name="/DataClassesDataContext/User/User_OAuthToken" /> <nodes> <classShapeMoniker Id="696d2c69-040e-411d-9257-bb664b743834" /> diff --git a/samples/OAuthServiceProvider/App_Code/DataClasses.designer.cs b/samples/OAuthServiceProvider/App_Code/DataClasses.designer.cs index 2fc532e..d9c2ba4 100644 --- a/samples/OAuthServiceProvider/App_Code/DataClasses.designer.cs +++ b/samples/OAuthServiceProvider/App_Code/DataClasses.designer.cs @@ -2,7 +2,7 @@ //------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. -// Runtime Version:2.0.50727.3053 +// Runtime Version:2.0.50727.4918 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -631,6 +631,8 @@ public partial class OAuthToken : INotifyPropertyChanging, INotifyPropertyChange private string _Scope; + private string _RequestTokenVerifier; + private EntityRef<OAuthConsumer> _OAuthConsumer; private EntityRef<User> _User; @@ -655,6 +657,8 @@ public partial class OAuthToken : INotifyPropertyChanging, INotifyPropertyChange partial void OnUserIdChanged(); partial void OnScopeChanging(string value); partial void OnScopeChanged(); + partial void OnRequestTokenVerifierChanging(string value); + partial void OnRequestTokenVerifierChanged(); #endregion public OAuthToken() @@ -832,6 +836,26 @@ public partial class OAuthToken : INotifyPropertyChanging, INotifyPropertyChange } } + [Column(Storage="_RequestTokenVerifier")] + public string RequestTokenVerifier + { + get + { + return this._RequestTokenVerifier; + } + set + { + if ((this._RequestTokenVerifier != value)) + { + this.OnRequestTokenVerifierChanging(value); + this.SendPropertyChanging(); + this._RequestTokenVerifier = value; + this.SendPropertyChanged("RequestTokenVerifier"); + this.OnRequestTokenVerifierChanged(); + } + } + } + [Association(Name="OAuthConsumer_OAuthToken", Storage="_OAuthConsumer", ThisKey="ConsumerId", OtherKey="ConsumerId", IsForeignKey=true, DeleteOnNull=true, DeleteRule="CASCADE")] public OAuthConsumer OAuthConsumer { diff --git a/samples/OAuthServiceProvider/App_Code/DatabaseTokenManager.cs b/samples/OAuthServiceProvider/App_Code/DatabaseTokenManager.cs index 275a7c9..fb1368f 100644 --- a/samples/OAuthServiceProvider/App_Code/DatabaseTokenManager.cs +++ b/samples/OAuthServiceProvider/App_Code/DatabaseTokenManager.cs @@ -24,6 +24,25 @@ public class DatabaseTokenManager : IServiceProviderTokenManager { return consumerRow.ConsumerSecret; } + public void SetRequestTokenVerifier(string requestToken, string verifier) { + if (String.IsNullOrEmpty(requestToken)) { + throw new ArgumentNullException("requestToken"); + } + if (String.IsNullOrEmpty(verifier)) { + throw new ArgumentNullException("verifier"); + } + + Global.DataContext.OAuthTokens.First(token => token.Token == requestToken).RequestTokenVerifier = verifier; + } + + public string GetRequestTokenVerifier(string requestToken) { + if (String.IsNullOrEmpty(requestToken)) { + throw new ArgumentNullException("requestToken"); + } + + return Global.DataContext.OAuthTokens.First(token => token.Token == requestToken).RequestTokenVerifier; + } + #endregion #region ITokenManager Members diff --git a/samples/OAuthServiceProvider/App_Code/RequestScopedTokenMessage.cs b/samples/OAuthServiceProvider/App_Code/RequestScopedTokenMessage.cs index b33a734..4cc4860 100644 --- a/samples/OAuthServiceProvider/App_Code/RequestScopedTokenMessage.cs +++ b/samples/OAuthServiceProvider/App_Code/RequestScopedTokenMessage.cs @@ -1,4 +1,5 @@ -using DotNetOpenAuth.Messaging; +using System; +using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OAuth.Messages; /// <summary> @@ -9,7 +10,8 @@ public class RequestScopedTokenMessage : UnauthorizedTokenRequest { /// Initializes a new instance of the <see cref="RequestScopedTokenMessage"/> class. /// </summary> /// <param name="endpoint">The endpoint that will receive the message.</param> - public RequestScopedTokenMessage(MessageReceivingEndpoint endpoint) : base(endpoint) { + /// <param name="version">The OAuth version.</param> + public RequestScopedTokenMessage(MessageReceivingEndpoint endpoint, Version version) : base(endpoint, version) { } /// <summary> diff --git a/src/DotNetOpenAuth.Test/Mocks/InMemoryTokenManager.cs b/src/DotNetOpenAuth.Test/Mocks/InMemoryTokenManager.cs index be3c563..bf9dc2b 100644 --- a/src/DotNetOpenAuth.Test/Mocks/InMemoryTokenManager.cs +++ b/src/DotNetOpenAuth.Test/Mocks/InMemoryTokenManager.cs @@ -15,6 +15,7 @@ namespace DotNetOpenAuth.Test.Mocks { internal class InMemoryTokenManager : IConsumerTokenManager, IServiceProviderTokenManager { private Dictionary<string, string> consumersAndSecrets = new Dictionary<string, string>(); private Dictionary<string, string> tokensAndSecrets = new Dictionary<string, string>(); + private Dictionary<string, string> tokensAndVerifiers = new Dictionary<string, string>(); /// <summary> /// Request tokens that have been issued, and whether they have been authorized yet. @@ -97,6 +98,14 @@ namespace DotNetOpenAuth.Test.Mocks { return this.consumersAndSecrets[consumerKey]; } + public void SetRequestTokenVerifier(string requestToken, string verifier) { + this.tokensAndVerifiers[requestToken] = verifier; + } + + public string GetRequestTokenVerifier(string requestToken) { + return this.tokensAndVerifiers[requestToken]; + } + #endregion /// <summary> diff --git a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/PlaintextSigningBindingElementTest.cs b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/PlaintextSigningBindingElementTest.cs index ca63b50..627db8f 100644 --- a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/PlaintextSigningBindingElementTest.cs +++ b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/PlaintextSigningBindingElementTest.cs @@ -4,9 +4,9 @@ // </copyright> //----------------------------------------------------------------------- -namespace DotNetOpenAuth.Test.ChannelElements -{ +namespace DotNetOpenAuth.Test.ChannelElements { using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth; using DotNetOpenAuth.OAuth.ChannelElements; using DotNetOpenAuth.OAuth.Messages; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -17,7 +17,7 @@ namespace DotNetOpenAuth.Test.ChannelElements public void HttpsSignatureGeneration() { SigningBindingElementBase target = new PlaintextSigningBindingElement(); MessageReceivingEndpoint endpoint = new MessageReceivingEndpoint("https://localtest", HttpDeliveryMethods.GetRequest); - ITamperResistantOAuthMessage message = new UnauthorizedTokenRequest(endpoint); + ITamperResistantOAuthMessage message = new UnauthorizedTokenRequest(endpoint, Protocol.Default.Version); message.ConsumerSecret = "cs"; message.TokenSecret = "ts"; Assert.IsNotNull(target.ProcessOutgoingMessage(message)); @@ -29,7 +29,7 @@ namespace DotNetOpenAuth.Test.ChannelElements public void HttpsSignatureVerification() { MessageReceivingEndpoint endpoint = new MessageReceivingEndpoint("https://localtest", HttpDeliveryMethods.GetRequest); ITamperProtectionChannelBindingElement target = new PlaintextSigningBindingElement(); - ITamperResistantOAuthMessage message = new UnauthorizedTokenRequest(endpoint); + ITamperResistantOAuthMessage message = new UnauthorizedTokenRequest(endpoint, Protocol.Default.Version); message.ConsumerSecret = "cs"; message.TokenSecret = "ts"; message.SignatureMethod = "PLAINTEXT"; @@ -41,7 +41,7 @@ namespace DotNetOpenAuth.Test.ChannelElements public void HttpsSignatureVerificationNotApplicable() { SigningBindingElementBase target = new PlaintextSigningBindingElement(); MessageReceivingEndpoint endpoint = new MessageReceivingEndpoint("https://localtest", HttpDeliveryMethods.GetRequest); - ITamperResistantOAuthMessage message = new UnauthorizedTokenRequest(endpoint); + ITamperResistantOAuthMessage message = new UnauthorizedTokenRequest(endpoint, Protocol.Default.Version); message.ConsumerSecret = "cs"; message.TokenSecret = "ts"; message.SignatureMethod = "ANOTHERALGORITHM"; @@ -53,7 +53,7 @@ namespace DotNetOpenAuth.Test.ChannelElements public void HttpSignatureGeneration() { SigningBindingElementBase target = new PlaintextSigningBindingElement(); MessageReceivingEndpoint endpoint = new MessageReceivingEndpoint("http://localtest", HttpDeliveryMethods.GetRequest); - ITamperResistantOAuthMessage message = new UnauthorizedTokenRequest(endpoint); + ITamperResistantOAuthMessage message = new UnauthorizedTokenRequest(endpoint, Protocol.Default.Version); message.ConsumerSecret = "cs"; message.TokenSecret = "ts"; @@ -67,7 +67,7 @@ namespace DotNetOpenAuth.Test.ChannelElements public void HttpSignatureVerification() { SigningBindingElementBase target = new PlaintextSigningBindingElement(); MessageReceivingEndpoint endpoint = new MessageReceivingEndpoint("http://localtest", HttpDeliveryMethods.GetRequest); - ITamperResistantOAuthMessage message = new UnauthorizedTokenRequest(endpoint); + ITamperResistantOAuthMessage message = new UnauthorizedTokenRequest(endpoint, Protocol.Default.Version); message.ConsumerSecret = "cs"; message.TokenSecret = "ts"; message.SignatureMethod = "PLAINTEXT"; diff --git a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/SigningBindingElementBaseTests.cs b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/SigningBindingElementBaseTests.cs index 93c0b3f..32ccf9e 100644 --- a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/SigningBindingElementBaseTests.cs +++ b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/SigningBindingElementBaseTests.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.Test.ChannelElements { using DotNetOpenAuth.Messaging; using DotNetOpenAuth.Messaging.Reflection; + using DotNetOpenAuth.OAuth; using DotNetOpenAuth.OAuth.ChannelElements; using DotNetOpenAuth.OAuth.Messages; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -63,7 +64,7 @@ namespace DotNetOpenAuth.Test.ChannelElements { internal static UnauthorizedTokenRequest CreateTestRequestTokenMessage(MessageDescriptionCollection messageDescriptions, MessageReceivingEndpoint endpoint) { endpoint = endpoint ?? new MessageReceivingEndpoint("https://www.google.com/accounts/OAuthGetRequestToken", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest); - UnauthorizedTokenRequest message = new UnauthorizedTokenRequest(endpoint); + UnauthorizedTokenRequest message = new UnauthorizedTokenRequest(endpoint, Protocol.Default.Version); message.ConsumerKey = "nerdbank.org"; ((ITamperResistantOAuthMessage)message).ConsumerSecret = "nerdbanksecret"; var signedMessage = (ITamperResistantOAuthMessage)message; diff --git a/src/DotNetOpenAuth.Test/OAuth/ProtocolTests.cs b/src/DotNetOpenAuth.Test/OAuth/ProtocolTests.cs index 6a2551a..a6c7306 100644 --- a/src/DotNetOpenAuth.Test/OAuth/ProtocolTests.cs +++ b/src/DotNetOpenAuth.Test/OAuth/ProtocolTests.cs @@ -23,12 +23,12 @@ namespace DotNetOpenAuth.Test { [TestMethod] public void AuthorizationHeaderScheme() { - Assert.AreEqual("OAuth", Protocol.V10.AuthorizationHeaderScheme); + Assert.AreEqual("OAuth", Protocol.AuthorizationHeaderScheme); } [TestMethod] public void ParameterPrefix() { - Assert.AreEqual("oauth_", Protocol.V10.ParameterPrefix); + Assert.AreEqual("oauth_", Protocol.ParameterPrefix); } } } diff --git a/src/DotNetOpenAuth.sln b/src/DotNetOpenAuth.sln index 171ab07..336f138 100644 --- a/src/DotNetOpenAuth.sln +++ b/src/DotNetOpenAuth.sln @@ -17,6 +17,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Specs", "Specs", "{CD57219F-24F4-4136-8741-6063D0D7A031}" ProjectSection(SolutionItems) = preProject ..\doc\specs\OAuth Core 1.0.htm = ..\doc\specs\OAuth Core 1.0.htm + ..\doc\specs\OAuth Core 1.0a (Draft 3).htm = ..\doc\specs\OAuth Core 1.0a (Draft 3).htm ..\doc\specs\openid-attribute-exchange-1_0.html = ..\doc\specs\openid-attribute-exchange-1_0.html ..\doc\specs\openid-authentication-1_1.html = ..\doc\specs\openid-authentication-1_1.html ..\doc\specs\openid-authentication-2_0.html = ..\doc\specs\openid-authentication-2_0.html diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj index 441c714..6f66c7c 100644 --- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj +++ b/src/DotNetOpenAuth/DotNetOpenAuth.csproj @@ -236,6 +236,8 @@ <Compile Include="OAuth\ChannelElements\SigningBindingElementChain.cs" /> <Compile Include="OAuth\ChannelElements\StandardTokenGenerator.cs" /> <Compile Include="OAuth\ChannelElements\TokenType.cs" /> + <Compile Include="OAuth\ChannelElements\UriOrOobEncoder.cs" /> + <Compile Include="OAuth\ChannelElements\VerificationCodeBindingElement.cs" /> <Compile Include="OAuth\ConsumerBase.cs" /> <Compile Include="OAuth\DesktopConsumer.cs" /> <Compile Include="GlobalSuppressions.cs" /> diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderTokenManager.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderTokenManager.cs index e1c1e3f..bc4a03d 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderTokenManager.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderTokenManager.cs @@ -23,5 +23,19 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// <exception cref="ArgumentException">Thrown if the consumer key cannot be found.</exception> /// <exception cref="InvalidOperationException">May be thrown if called when the signature algorithm does not require a consumer secret, such as when RSA-SHA1 is used.</exception> string GetConsumerSecret(string consumerKey); + + /// <summary> + /// Sets the verifier code associated with an authorized request token. + /// </summary> + /// <param name="requestToken">The request token.</param> + /// <param name="verifier">The verification code.</param> + void SetRequestTokenVerifier(string requestToken, string verifier); + + /// <summary> + /// Gets the verifier code associated with an authorized request token. + /// </summary> + /// <param name="requestToken">The request token that the Consumer is exchanging for an access token.</param> + /// <returns>The verifier code that was generated when previously authorizing the request token.</returns> + string GetRequestTokenVerifier(string requestToken); } } diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs index 5ac1aa8..f0aa64a 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs @@ -63,7 +63,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// <see cref="OAuthConsumerMessageFactory"/> or <see cref="OAuthServiceProviderMessageFactory"/>. /// </param> internal OAuthChannel(ITamperProtectionChannelBindingElement signingBindingElement, INonceStore store, ITokenManager tokenManager, IMessageFactory messageTypeProvider) - : base(messageTypeProvider, new OAuthHttpMethodBindingElement(), signingBindingElement, new StandardExpirationBindingElement(), new StandardReplayProtectionBindingElement(store)) { + : base(messageTypeProvider, InitializeBindingElements(signingBindingElement, store, tokenManager)) { ErrorUtilities.VerifyArgumentNotNull(tokenManager, "tokenManager"); this.TokenManager = tokenManager; @@ -122,7 +122,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { string authorization = request.Headers[HttpRequestHeader.Authorization]; if (authorization != null) { string[] authorizationSections = authorization.Split(';'); // TODO: is this the right delimiter? - string oauthPrefix = Protocol.Default.AuthorizationHeaderScheme + " "; + string oauthPrefix = Protocol.AuthorizationHeaderScheme + " "; // The Authorization header may have multiple uses, and OAuth may be just one of them. // Go through each one looking for an OAuth one. @@ -241,6 +241,29 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { } /// <summary> + /// Initializes the binding elements for the OAuth channel. + /// </summary> + /// <param name="signingBindingElement">The signing binding element.</param> + /// <param name="store">The nonce store.</param> + /// <param name="tokenManager">The token manager.</param> + /// <returns>An array of binding elements used to initialize the channel.</returns> + private static IChannelBindingElement[] InitializeBindingElements(ITamperProtectionChannelBindingElement signingBindingElement, INonceStore store, ITokenManager tokenManager) { + var bindingElements = new List<IChannelBindingElement> { + new OAuthHttpMethodBindingElement(), + signingBindingElement, + new StandardExpirationBindingElement(), + new StandardReplayProtectionBindingElement(store), + }; + + var spTokenManager = tokenManager as IServiceProviderTokenManager; + if (spTokenManager != null) { + bindingElements.Insert(0, new VerificationCodeBindingElement(spTokenManager)); + } + + return bindingElements.ToArray(); + } + + /// <summary> /// Uri-escapes the names and values in a dictionary per OAuth 1.0 section 5.1. /// </summary> /// <param name="source">The dictionary with names and values to encode.</param> @@ -307,7 +330,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { httpRequest.Method = GetHttpMethod(requestMessage); StringBuilder authorization = new StringBuilder(); - authorization.Append(protocol.AuthorizationHeaderScheme); + authorization.Append(Protocol.AuthorizationHeaderScheme); authorization.Append(" "); foreach (var pair in fields) { string key = MessagingUtilities.EscapeUriDataStringRfc3986(pair.Key); diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthConsumerMessageFactory.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthConsumerMessageFactory.cs index fce351b..3f18d82 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthConsumerMessageFactory.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthConsumerMessageFactory.cs @@ -44,7 +44,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { MessageBase message = null; if (fields.ContainsKey("oauth_token")) { - message = new UserAuthorizationResponse(recipient.Location); + message = new UserAuthorizationResponse(recipient.Location, Protocol.Default.Version); } if (message != null) { @@ -92,9 +92,10 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { var unauthorizedTokenRequest = request as UnauthorizedTokenRequest; var authorizedTokenRequest = request as AuthorizedTokenRequest; if (unauthorizedTokenRequest != null) { - message = new UnauthorizedTokenResponse(unauthorizedTokenRequest); + Protocol protocol = fields.ContainsKey("oauth_callback_confirmed") ? Protocol.V10a : Protocol.V10; + message = new UnauthorizedTokenResponse(unauthorizedTokenRequest, protocol.Version); } else if (authorizedTokenRequest != null) { - message = new AuthorizedTokenResponse(authorizedTokenRequest); + message = new AuthorizedTokenResponse(authorizedTokenRequest, Protocol.Default.Version); } else { Logger.OAuth.ErrorFormat("Unexpected response message given the request type {0}", request.GetType().Name); throw new ProtocolException(OAuthStrings.InvalidIncomingMessage); diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthServiceProviderMessageFactory.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthServiceProviderMessageFactory.cs index 1aaea7f..b3f750e 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthServiceProviderMessageFactory.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthServiceProviderMessageFactory.cs @@ -55,9 +55,9 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { MessageBase message = null; - if (fields.ContainsKey("oauth_consumer_key") && - !fields.ContainsKey("oauth_token")) { - message = new UnauthorizedTokenRequest(recipient); + if (fields.ContainsKey("oauth_consumer_key") && !fields.ContainsKey("oauth_token")) { + Protocol protocol = fields.ContainsKey("oauth_callback") ? Protocol.V10a : Protocol.V10; + message = new UnauthorizedTokenRequest(recipient, protocol.Version); } else if (fields.ContainsKey("oauth_consumer_key") && fields.ContainsKey("oauth_token")) { // Discern between RequestAccessToken and AccessProtectedResources, @@ -65,11 +65,11 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { // is in the token parameter. bool tokenTypeIsAccessToken = this.tokenManager.GetTokenType(fields["oauth_token"]) == TokenType.AccessToken; - message = tokenTypeIsAccessToken ? (MessageBase)new AccessProtectedResourceRequest(recipient) : - new AuthorizedTokenRequest(recipient); + message = tokenTypeIsAccessToken ? (MessageBase)new AccessProtectedResourceRequest(recipient, Protocol.Default.Version) : + new AuthorizedTokenRequest(recipient, Protocol.Default.Version); } else { // fail over to the message with no required fields at all. - message = new UserAuthorizationRequest(recipient); + message = new UserAuthorizationRequest(recipient, Protocol.Default.Version); } if (message != null) { diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/UriOrOobEncoder.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/UriOrOobEncoder.cs new file mode 100644 index 0000000..9652e24 --- /dev/null +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/UriOrOobEncoder.cs @@ -0,0 +1,62 @@ +//----------------------------------------------------------------------- +// <copyright file="UriOrOobEncoder.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth.ChannelElements { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging.Reflection; + + /// <summary> + /// An URI encoder that translates null <see cref="Uri"/> references as "oob" + /// instead of an empty/missing argument. + /// </summary> + internal class UriOrOobEncoder : IMessagePartEncoder { + /// <summary> + /// The string constant "oob", used to indicate an out-of-band configuration. + /// </summary> + private const string OutOfBandConfiguration = "oob"; + + /// <summary> + /// Initializes a new instance of the <see cref="UriOrOobEncoder"/> class. + /// </summary> + internal UriOrOobEncoder() { + } + + #region IMessagePartEncoder Members + + /// <summary> + /// Encodes the specified value. + /// </summary> + /// <param name="value">The value. Guaranteed to never be null.</param> + /// <returns> + /// The <paramref name="value"/> in string form, ready for message transport. + /// </returns> + public string Encode(object value) { + Uri uriValue = (Uri)value; + return uriValue != null ? uriValue.AbsoluteUri : OutOfBandConfiguration; + } + + /// <summary> + /// Decodes the specified value. + /// </summary> + /// <param name="value">The string value carried by the transport. Guaranteed to never be null, although it may be empty.</param> + /// <returns> + /// The deserialized form of the given string. + /// </returns> + /// <exception cref="FormatException">Thrown when the string value given cannot be decoded into the required object type.</exception> + public object Decode(string value) { + if (string.Equals(value, OutOfBandConfiguration, StringComparison.Ordinal)) { + return null; + } else { + return new Uri(value, UriKind.Absolute); + } + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/VerificationCodeBindingElement.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/VerificationCodeBindingElement.cs new file mode 100644 index 0000000..f1e14f4 --- /dev/null +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/VerificationCodeBindingElement.cs @@ -0,0 +1,119 @@ +//----------------------------------------------------------------------- +// <copyright file="VerificationCodeBindingElement.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth.ChannelElements { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth.Messages; + + /// <summary> + /// A binding element for Service Providers to manage the verification code on applicable messages. + /// </summary> + internal class VerificationCodeBindingElement : IChannelBindingElement { + /// <summary> + /// The length of the verifier code (in raw bytes before base64 encoding) to generate. + /// </summary> + private const int VerifierCodeLength = 5; + + /// <summary> + /// The token manager offered by the service provider. + /// </summary> + private IServiceProviderTokenManager tokenManager; + + /// <summary> + /// Initializes a new instance of the <see cref="VerificationCodeBindingElement"/> class. + /// </summary> + /// <param name="tokenManager">The token manager.</param> + internal VerificationCodeBindingElement(IServiceProviderTokenManager tokenManager) { + Contract.Requires(tokenManager != null); + ErrorUtilities.VerifyArgumentNotNull(tokenManager, "tokenManager"); + + this.tokenManager = tokenManager; + } + + #region IChannelBindingElement Members + + /// <summary> + /// Gets or sets the channel that this binding element belongs to. + /// </summary> + /// <remarks> + /// This property is set by the channel when it is first constructed. + /// </remarks> + public Channel Channel { get; set; } + + /// <summary> + /// Gets the protection commonly offered (if any) by this binding element. + /// </summary> + /// <remarks> + /// This value is used to assist in sorting binding elements in the channel stack. + /// </remarks> + public MessageProtections Protection { + get { return MessageProtections.None; } + } + + /// <summary> + /// Prepares a message for sending based on the rules of this channel binding element. + /// </summary> + /// <param name="message">The message to prepare for sending.</param> + /// <returns> + /// The protections (if any) that this binding element applied to the message. + /// Null if this binding element did not even apply to this binding element. + /// </returns> + /// <remarks> + /// Implementations that provide message protection must honor the + /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable. + /// </remarks> + public MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { + ErrorUtilities.VerifyArgumentNotNull(message, "message"); + + var response = message as UserAuthorizationResponse; + if (response != null && response.Version >= Protocol.V10a.Version) { + ErrorUtilities.VerifyInternal(response.VerificationCode == null, "VerificationCode was unexpectedly already set."); + response.VerificationCode = MessagingUtilities.GetCryptoRandomDataAsBase64(VerifierCodeLength); + this.tokenManager.SetRequestTokenVerifier(response.RequestToken, response.VerificationCode); + return MessageProtections.None; + } + + return null; + } + + /// <summary> + /// Performs any transformation on an incoming message that may be necessary and/or + /// validates an incoming message based on the rules of this channel binding element. + /// </summary> + /// <param name="message">The incoming message to process.</param> + /// <returns> + /// The protections (if any) that this binding element applied to the message. + /// Null if this binding element did not even apply to this binding element. + /// </returns> + /// <exception cref="ProtocolException"> + /// Thrown when the binding element rules indicate that this message is invalid and should + /// NOT be processed. + /// </exception> + /// <remarks> + /// Implementations that provide message protection must honor the + /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable. + /// </remarks> + public MessageProtections? ProcessIncomingMessage(IProtocolMessage message) { + ErrorUtilities.VerifyArgumentNotNull(message, "message"); + + var request = message as AuthorizedTokenRequest; + if (request != null && request.Version >= Protocol.V10a.Version) { + string expectedVerifier = this.tokenManager.GetRequestTokenVerifier(request.RequestToken); + ErrorUtilities.VerifyProtocol(string.Equals(request.VerificationCode, expectedVerifier, StringComparison.Ordinal), OAuthStrings.IncorrectVerifier); + return MessageProtections.None; + } + + return null; + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth/OAuth/ConsumerBase.cs b/src/DotNetOpenAuth/OAuth/ConsumerBase.cs index 2392172..81da0ac 100644 --- a/src/DotNetOpenAuth/OAuth/ConsumerBase.cs +++ b/src/DotNetOpenAuth/OAuth/ConsumerBase.cs @@ -167,7 +167,7 @@ namespace DotNetOpenAuth.OAuth { ErrorUtilities.VerifyArgumentNotNull(endpoint, "endpoint"); ErrorUtilities.VerifyNonZeroLength(accessToken, "accessToken"); - AccessProtectedResourceRequest message = new AccessProtectedResourceRequest(endpoint) { + AccessProtectedResourceRequest message = new AccessProtectedResourceRequest(endpoint, Protocol.Default.Version) { AccessToken = accessToken, ConsumerKey = this.ConsumerKey, }; @@ -190,7 +190,7 @@ namespace DotNetOpenAuth.OAuth { [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "3#", Justification = "Two results")] protected internal UserAuthorizationRequest PrepareRequestUserAuthorization(Uri callback, IDictionary<string, string> requestParameters, IDictionary<string, string> redirectParameters, out string requestToken) { // Obtain an unauthorized request token. - var token = new UnauthorizedTokenRequest(this.ServiceProvider.RequestTokenEndpoint) { + var token = new UnauthorizedTokenRequest(this.ServiceProvider.RequestTokenEndpoint, Protocol.Default.Version) { ConsumerKey = this.ConsumerKey, }; var tokenAccessor = this.Channel.MessageDescriptions.GetAccessor(token); @@ -200,7 +200,7 @@ namespace DotNetOpenAuth.OAuth { // Request user authorization. ITokenContainingMessage assignedRequestToken = requestTokenResponse; - var requestAuthorization = new UserAuthorizationRequest(this.ServiceProvider.UserAuthorizationEndpoint, assignedRequestToken.Token) { + var requestAuthorization = new UserAuthorizationRequest(this.ServiceProvider.UserAuthorizationEndpoint, assignedRequestToken.Token, Protocol.Default.Version) { Callback = callback, }; var requestAuthorizationAccessor = this.Channel.MessageDescriptions.GetAccessor(requestAuthorization); @@ -215,7 +215,7 @@ namespace DotNetOpenAuth.OAuth { /// <param name="requestToken">The request token that the user has authorized.</param> /// <returns>The access token assigned by the Service Provider.</returns> protected AuthorizedTokenResponse ProcessUserAuthorization(string requestToken) { - var requestAccess = new AuthorizedTokenRequest(this.ServiceProvider.AccessTokenEndpoint) { + var requestAccess = new AuthorizedTokenRequest(this.ServiceProvider.AccessTokenEndpoint, Protocol.Default.Version) { RequestToken = requestToken, ConsumerKey = this.ConsumerKey, }; diff --git a/src/DotNetOpenAuth/OAuth/Messages/AccessProtectedResourceRequest.cs b/src/DotNetOpenAuth/OAuth/Messages/AccessProtectedResourceRequest.cs index 62e02de..b60fda4 100644 --- a/src/DotNetOpenAuth/OAuth/Messages/AccessProtectedResourceRequest.cs +++ b/src/DotNetOpenAuth/OAuth/Messages/AccessProtectedResourceRequest.cs @@ -5,6 +5,7 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.OAuth.Messages { + using System; using System.Diagnostics.CodeAnalysis; using DotNetOpenAuth.Messaging; @@ -17,8 +18,9 @@ namespace DotNetOpenAuth.OAuth.Messages { /// Initializes a new instance of the <see cref="AccessProtectedResourceRequest"/> class. /// </summary> /// <param name="serviceProvider">The URI of the Service Provider endpoint to send this message to.</param> - protected internal AccessProtectedResourceRequest(MessageReceivingEndpoint serviceProvider) - : base(MessageTransport.Direct, serviceProvider) { + /// <param name="version">The OAuth version.</param> + protected internal AccessProtectedResourceRequest(MessageReceivingEndpoint serviceProvider, Version version) + : base(MessageTransport.Direct, serviceProvider, version) { } /// <summary> diff --git a/src/DotNetOpenAuth/OAuth/Messages/AuthorizedTokenRequest.cs b/src/DotNetOpenAuth/OAuth/Messages/AuthorizedTokenRequest.cs index 2d4793c..c96e82b 100644 --- a/src/DotNetOpenAuth/OAuth/Messages/AuthorizedTokenRequest.cs +++ b/src/DotNetOpenAuth/OAuth/Messages/AuthorizedTokenRequest.cs @@ -5,6 +5,7 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.OAuth.Messages { + using System; using System.Globalization; using DotNetOpenAuth.Messaging; @@ -20,8 +21,9 @@ namespace DotNetOpenAuth.OAuth.Messages { /// Initializes a new instance of the <see cref="AuthorizedTokenRequest"/> class. /// </summary> /// <param name="serviceProvider">The URI of the Service Provider endpoint to send this message to.</param> - internal AuthorizedTokenRequest(MessageReceivingEndpoint serviceProvider) - : base(MessageTransport.Direct, serviceProvider) { + /// <param name="version">The OAuth version.</param> + internal AuthorizedTokenRequest(MessageReceivingEndpoint serviceProvider, Version version) + : base(MessageTransport.Direct, serviceProvider, version) { } /// <summary> @@ -39,6 +41,13 @@ namespace DotNetOpenAuth.OAuth.Messages { internal string RequestToken { get; set; } /// <summary> + /// Gets or sets the verification code received by the Consumer from the Service Provider + /// in the <see cref="UserAuthorizationResponse.VerificationCode"/> property. + /// </summary> + [MessagePart("oauth_verifier", IsRequired = true, AllowEmpty = false, MinVersion = Protocol.V10aVersion)] + internal string VerificationCode { get; set; } + + /// <summary> /// Checks the message state for conformity to the protocol specification /// and throws an exception if the message is invalid. /// </summary> diff --git a/src/DotNetOpenAuth/OAuth/Messages/AuthorizedTokenResponse.cs b/src/DotNetOpenAuth/OAuth/Messages/AuthorizedTokenResponse.cs index 14413a5..d87c381 100644 --- a/src/DotNetOpenAuth/OAuth/Messages/AuthorizedTokenResponse.cs +++ b/src/DotNetOpenAuth/OAuth/Messages/AuthorizedTokenResponse.cs @@ -5,6 +5,7 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.OAuth.Messages { + using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using DotNetOpenAuth.Messaging; @@ -18,8 +19,9 @@ namespace DotNetOpenAuth.OAuth.Messages { /// Initializes a new instance of the <see cref="AuthorizedTokenResponse"/> class. /// </summary> /// <param name="originatingRequest">The originating request.</param> - protected internal AuthorizedTokenResponse(AuthorizedTokenRequest originatingRequest) - : base(MessageProtections.None, originatingRequest) { + /// <param name="version">The OAuth version.</param> + protected internal AuthorizedTokenResponse(AuthorizedTokenRequest originatingRequest, Version version) + : base(MessageProtections.None, originatingRequest, version) { } /// <summary> diff --git a/src/DotNetOpenAuth/OAuth/Messages/MessageBase.cs b/src/DotNetOpenAuth/OAuth/Messages/MessageBase.cs index e0269db..89e0276 100644 --- a/src/DotNetOpenAuth/OAuth/Messages/MessageBase.cs +++ b/src/DotNetOpenAuth/OAuth/Messages/MessageBase.cs @@ -62,12 +62,15 @@ namespace DotNetOpenAuth.OAuth.Messages { /// </summary> /// <param name="protectionRequired">The level of protection the message requires.</param> /// <param name="originatingRequest">The request that asked for this direct response.</param> - protected MessageBase(MessageProtections protectionRequired, IDirectedProtocolMessage originatingRequest) { + /// <param name="version">The OAuth version.</param> + protected MessageBase(MessageProtections protectionRequired, IDirectedProtocolMessage originatingRequest, Version version) { ErrorUtilities.VerifyArgumentNotNull(originatingRequest, "originatingRequest"); + ErrorUtilities.VerifyArgumentNotNull(version, "version"); this.protectionRequired = protectionRequired; this.transport = MessageTransport.Direct; this.originatingRequest = originatingRequest; + this.Version = version; } /// <summary> @@ -76,14 +79,15 @@ namespace DotNetOpenAuth.OAuth.Messages { /// <param name="protectionRequired">The level of protection the message requires.</param> /// <param name="transport">A value indicating whether this message requires a direct or indirect transport.</param> /// <param name="recipient">The URI that a directed message will be delivered to.</param> - protected MessageBase(MessageProtections protectionRequired, MessageTransport transport, MessageReceivingEndpoint recipient) { - if (recipient == null) { - throw new ArgumentNullException("recipient"); - } + /// <param name="version">The OAuth version.</param> + protected MessageBase(MessageProtections protectionRequired, MessageTransport transport, MessageReceivingEndpoint recipient, Version version) { + ErrorUtilities.VerifyArgumentNotNull(recipient, "recipient"); + ErrorUtilities.VerifyArgumentNotNull(version, "version"); this.protectionRequired = protectionRequired; this.transport = transport; this.recipient = recipient; + this.Version = version; } #region IProtocolMessage Properties @@ -163,9 +167,7 @@ namespace DotNetOpenAuth.OAuth.Messages { /// <summary> /// Gets the version of the protocol this message is prepared to implement. /// </summary> - protected virtual Version Version { - get { return new Version(1, 0); } - } + protected internal Version Version { get; private set; } /// <summary> /// Gets the level of protection this message requires. diff --git a/src/DotNetOpenAuth/OAuth/Messages/SignedMessageBase.cs b/src/DotNetOpenAuth/OAuth/Messages/SignedMessageBase.cs index d1abb58..eb85cb5 100644 --- a/src/DotNetOpenAuth/OAuth/Messages/SignedMessageBase.cs +++ b/src/DotNetOpenAuth/OAuth/Messages/SignedMessageBase.cs @@ -31,8 +31,9 @@ namespace DotNetOpenAuth.OAuth.Messages { /// </summary> /// <param name="transport">A value indicating whether this message requires a direct or indirect transport.</param> /// <param name="recipient">The URI that a directed message will be delivered to.</param> - internal SignedMessageBase(MessageTransport transport, MessageReceivingEndpoint recipient) - : base(MessageProtections.All, transport, recipient) { + /// <param name="version">The OAuth version.</param> + internal SignedMessageBase(MessageTransport transport, MessageReceivingEndpoint recipient, Version version) + : base(MessageProtections.All, transport, recipient, version) { ITamperResistantOAuthMessage self = (ITamperResistantOAuthMessage)this; HttpDeliveryMethods methods = ((IDirectedProtocolMessage)this).HttpMethods; self.HttpMethod = (methods & HttpDeliveryMethods.PostRequest) != 0 ? "POST" : "GET"; diff --git a/src/DotNetOpenAuth/OAuth/Messages/UnauthorizedTokenRequest.cs b/src/DotNetOpenAuth/OAuth/Messages/UnauthorizedTokenRequest.cs index e491bad..fceac01 100644 --- a/src/DotNetOpenAuth/OAuth/Messages/UnauthorizedTokenRequest.cs +++ b/src/DotNetOpenAuth/OAuth/Messages/UnauthorizedTokenRequest.cs @@ -5,8 +5,10 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.OAuth.Messages { + using System; using System.Collections.Generic; using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth.ChannelElements; /// <summary> /// A direct message sent from Consumer to Service Provider to request a Request Token. @@ -16,11 +18,23 @@ namespace DotNetOpenAuth.OAuth.Messages { /// Initializes a new instance of the <see cref="UnauthorizedTokenRequest"/> class. /// </summary> /// <param name="serviceProvider">The URI of the Service Provider endpoint to send this message to.</param> - protected internal UnauthorizedTokenRequest(MessageReceivingEndpoint serviceProvider) - : base(MessageTransport.Direct, serviceProvider) { + /// <param name="version">The OAuth version.</param> + protected internal UnauthorizedTokenRequest(MessageReceivingEndpoint serviceProvider, Version version) + : base(MessageTransport.Direct, serviceProvider, version) { } /// <summary> + /// Gets or sets the absolute URL to which the Service Provider will redirect the + /// User back when the Obtaining User Authorization step is completed. + /// </summary> + /// <value> + /// The callback URL; or <c>null</c> if the Consumer is unable to receive + /// callbacks or a callback URL has been established via other means. + /// </value> + [MessagePart("oauth_callback", IsRequired = true, AllowEmpty = false, MinVersion = Protocol.V10aVersion, Encoder = typeof(UriOrOobEncoder))] + public Uri Callback { get; set; } + + /// <summary> /// Gets the extra, non-OAuth parameters that will be included in the message. /// </summary> public new IDictionary<string, string> ExtraData { diff --git a/src/DotNetOpenAuth/OAuth/Messages/UnauthorizedTokenResponse.cs b/src/DotNetOpenAuth/OAuth/Messages/UnauthorizedTokenResponse.cs index 285dec7..23a138c 100644 --- a/src/DotNetOpenAuth/OAuth/Messages/UnauthorizedTokenResponse.cs +++ b/src/DotNetOpenAuth/OAuth/Messages/UnauthorizedTokenResponse.cs @@ -21,11 +21,12 @@ namespace DotNetOpenAuth.OAuth.Messages { /// <param name="requestMessage">The unauthorized request token message that this message is being generated in response to.</param> /// <param name="requestToken">The request token.</param> /// <param name="tokenSecret">The token secret.</param> + /// <param name="version">The OAuth version.</param> /// <remarks> /// This constructor is used by the Service Provider to send the message. /// </remarks> - protected internal UnauthorizedTokenResponse(UnauthorizedTokenRequest requestMessage, string requestToken, string tokenSecret) - : this(requestMessage) { + protected internal UnauthorizedTokenResponse(UnauthorizedTokenRequest requestMessage, string requestToken, string tokenSecret, Version version) + : this(requestMessage, version) { ErrorUtilities.VerifyArgumentNotNull(requestToken, "requestToken"); ErrorUtilities.VerifyArgumentNotNull(tokenSecret, "tokenSecret"); @@ -37,9 +38,10 @@ namespace DotNetOpenAuth.OAuth.Messages { /// Initializes a new instance of the <see cref="UnauthorizedTokenResponse"/> class. /// </summary> /// <param name="originatingRequest">The originating request.</param> + /// <param name="version">The OAuth version.</param> /// <remarks>This constructor is used by the consumer to deserialize the message.</remarks> - protected internal UnauthorizedTokenResponse(UnauthorizedTokenRequest originatingRequest) - : base(MessageProtections.None, originatingRequest) { + protected internal UnauthorizedTokenResponse(UnauthorizedTokenRequest originatingRequest, Version version) + : base(MessageProtections.None, originatingRequest, version) { } /// <summary> @@ -84,5 +86,13 @@ namespace DotNetOpenAuth.OAuth.Messages { /// </summary> [MessagePart("oauth_token_secret", IsRequired = true)] protected internal string TokenSecret { get; set; } + + /// <summary> + /// Gets a value indicating whether the Service Provider recognized the callback parameter in the request. + /// </summary> + [MessagePart("oauth_callback_confirmed", IsRequired = true, MinVersion = Protocol.V10aVersion)] + private bool CallbackConfirmed { + get { return true; } + } } } diff --git a/src/DotNetOpenAuth/OAuth/Messages/UserAuthorizationRequest.cs b/src/DotNetOpenAuth/OAuth/Messages/UserAuthorizationRequest.cs index f1af0bc..0924ac6 100644 --- a/src/DotNetOpenAuth/OAuth/Messages/UserAuthorizationRequest.cs +++ b/src/DotNetOpenAuth/OAuth/Messages/UserAuthorizationRequest.cs @@ -21,8 +21,9 @@ namespace DotNetOpenAuth.OAuth.Messages { /// </summary> /// <param name="serviceProvider">The URI of the Service Provider endpoint to send this message to.</param> /// <param name="requestToken">The request token.</param> - internal UserAuthorizationRequest(MessageReceivingEndpoint serviceProvider, string requestToken) - : this(serviceProvider) { + /// <param name="version">The OAuth version.</param> + internal UserAuthorizationRequest(MessageReceivingEndpoint serviceProvider, string requestToken, Version version) + : this(serviceProvider, version) { this.RequestToken = requestToken; } @@ -30,8 +31,9 @@ namespace DotNetOpenAuth.OAuth.Messages { /// Initializes a new instance of the <see cref="UserAuthorizationRequest"/> class. /// </summary> /// <param name="serviceProvider">The URI of the Service Provider endpoint to send this message to.</param> - internal UserAuthorizationRequest(MessageReceivingEndpoint serviceProvider) - : base(MessageProtections.None, MessageTransport.Indirect, serviceProvider) { + /// <param name="version">The OAuth version.</param> + internal UserAuthorizationRequest(MessageReceivingEndpoint serviceProvider, Version version) + : base(MessageProtections.None, MessageTransport.Indirect, serviceProvider, version) { } /// <summary> @@ -65,7 +67,7 @@ namespace DotNetOpenAuth.OAuth.Messages { /// Gets or sets a URL the Service Provider will use to redirect the User back /// to the Consumer when Obtaining User Authorization is complete. Optional. /// </summary> - [MessagePart("oauth_callback", IsRequired = false)] + [MessagePart("oauth_callback", IsRequired = false, MaxVersion = "1.0")] internal Uri Callback { get; set; } } } diff --git a/src/DotNetOpenAuth/OAuth/Messages/UserAuthorizationResponse.cs b/src/DotNetOpenAuth/OAuth/Messages/UserAuthorizationResponse.cs index da6a909..a9a43fb 100644 --- a/src/DotNetOpenAuth/OAuth/Messages/UserAuthorizationResponse.cs +++ b/src/DotNetOpenAuth/OAuth/Messages/UserAuthorizationResponse.cs @@ -19,8 +19,9 @@ namespace DotNetOpenAuth.OAuth.Messages { /// Initializes a new instance of the <see cref="UserAuthorizationResponse"/> class. /// </summary> /// <param name="consumer">The URI of the Consumer endpoint to send this message to.</param> - internal UserAuthorizationResponse(Uri consumer) - : base(MessageProtections.None, MessageTransport.Indirect, new MessageReceivingEndpoint(consumer, HttpDeliveryMethods.GetRequest)) { + /// <param name="version">The OAuth version.</param> + internal UserAuthorizationResponse(Uri consumer, Version version) + : base(MessageProtections.None, MessageTransport.Indirect, new MessageReceivingEndpoint(consumer, HttpDeliveryMethods.GetRequest), version) { } /// <summary> @@ -36,5 +37,19 @@ namespace DotNetOpenAuth.OAuth.Messages { /// </summary> [MessagePart("oauth_token", IsRequired = true)] internal string RequestToken { get; set; } + + /// <summary> + /// Gets or sets the verification code that must accompany the request to exchange the + /// authorized request token for an access token. + /// </summary> + /// <value>An unguessable value passed to the Consumer via the User and REQUIRED to complete the process.</value> + /// <remarks> + /// If the Consumer did not provide a callback URL, the Service Provider SHOULD display the value of the + /// verification code, and instruct the User to manually inform the Consumer that authorization is + /// completed. If the Service Provider knows a Consumer to be running on a mobile device or set-top box, + /// the Service Provider SHOULD ensure that the verifier value is suitable for manual entry. + /// </remarks> + [MessagePart("oauth_verifier", IsRequired = true, AllowEmpty = false, MinVersion = Protocol.V10aVersion)] + internal string VerificationCode { get; set; } } } diff --git a/src/DotNetOpenAuth/OAuth/OAuthStrings.Designer.cs b/src/DotNetOpenAuth/OAuth/OAuthStrings.Designer.cs index 63e348a..d1811c4 100644 --- a/src/DotNetOpenAuth/OAuth/OAuthStrings.Designer.cs +++ b/src/DotNetOpenAuth/OAuth/OAuthStrings.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. -// Runtime Version:2.0.50727.3521 +// Runtime Version:2.0.50727.4918 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -97,6 +97,15 @@ namespace DotNetOpenAuth.OAuth { } /// <summary> + /// Looks up a localized string similar to oauth_verifier argument was incorrect.. + /// </summary> + internal static string IncorrectVerifier { + get { + return ResourceManager.GetString("IncorrectVerifier", resourceCulture); + } + } + + /// <summary> /// Looks up a localized string similar to An invalid OAuth message received and discarded.. /// </summary> internal static string InvalidIncomingMessage { diff --git a/src/DotNetOpenAuth/OAuth/OAuthStrings.resx b/src/DotNetOpenAuth/OAuth/OAuthStrings.resx index 3ba4da1..2e82bdb 100644 --- a/src/DotNetOpenAuth/OAuth/OAuthStrings.resx +++ b/src/DotNetOpenAuth/OAuth/OAuthStrings.resx @@ -129,6 +129,9 @@ <data name="ConsumerOrTokenSecretNotFound" xml:space="preserve"> <value>Failure looking up secret for consumer or token.</value> </data> + <data name="IncorrectVerifier" xml:space="preserve"> + <value>oauth_verifier argument was incorrect.</value> + </data> <data name="InvalidIncomingMessage" xml:space="preserve"> <value>An invalid OAuth message received and discarded.</value> </data> diff --git a/src/DotNetOpenAuth/OAuth/Protocol.cs b/src/DotNetOpenAuth/OAuth/Protocol.cs index 88615ff..9417efa 100644 --- a/src/DotNetOpenAuth/OAuth/Protocol.cs +++ b/src/DotNetOpenAuth/OAuth/Protocol.cs @@ -12,6 +12,21 @@ namespace DotNetOpenAuth.OAuth { using DotNetOpenAuth.Messaging; /// <summary> + /// An enumeration of the OAuth protocol versions supported by this library. + /// </summary> + public enum ProtocolVersion { + /// <summary> + /// OAuth 1.0 specification + /// </summary> + V10, + + /// <summary> + /// OAuth 1.0a specification + /// </summary> + V10a, + } + + /// <summary> /// Constants used in the OAuth protocol. /// </summary> /// <remarks> @@ -25,63 +40,92 @@ namespace DotNetOpenAuth.OAuth { internal const string DataContractNamespaceV10 = "http://oauth.net/core/1.0/"; /// <summary> + /// The prefix used for all key names in the protocol. + /// </summary> + internal const string ParameterPrefix = "oauth_"; + + /// <summary> + /// The string representation of a <see cref="Version"/> instance to be used to represent OAuth 1.0a. + /// </summary> + internal const string V10aVersion = "1.0.1"; + + /// <summary> + /// The scheme to use in Authorization header message requests. + /// </summary> + internal const string AuthorizationHeaderScheme = "OAuth"; + + /// <summary> /// Gets the <see cref="Protocol"/> instance with values initialized for V1.0 of the protocol. /// </summary> internal static readonly Protocol V10 = new Protocol { dataContractNamespace = DataContractNamespaceV10, + Version = new Version(1, 0), }; /// <summary> - /// The namespace to use for this version of the protocol. + /// Gets the <see cref="Protocol"/> instance with values initialized for V1.0a of the protocol. /// </summary> - private string dataContractNamespace; + internal static readonly Protocol V10a = new Protocol { + dataContractNamespace = DataContractNamespaceV10, + Version = new Version(V10aVersion), + }; /// <summary> - /// The prefix used for all key names in the protocol. + /// A list of all supported OAuth versions, in order starting from newest version. /// </summary> - private string parameterPrefix = "oauth_"; + internal static readonly List<Protocol> AllVersions = new List<Protocol>() { V10a, V10 }; /// <summary> - /// The scheme to use in Authorization header message requests. + /// The default (or most recent) supported version of the OpenID protocol. /// </summary> - private string authorizationHeaderScheme = "OAuth"; + internal static readonly Protocol Default = AllVersions[0]; /// <summary> - /// Gets the default <see cref="Protocol"/> instance. + /// The namespace to use for this version of the protocol. /// </summary> - internal static Protocol Default { get { return V10; } } + private string dataContractNamespace; /// <summary> - /// Gets the namespace to use for this version of the protocol. + /// Initializes a new instance of the <see cref="Protocol"/> class. /// </summary> - internal string DataContractNamespace { - get { return this.dataContractNamespace; } + internal Protocol() { } /// <summary> - /// Gets the prefix used for all key names in the protocol. + /// Gets the version used to represent OAuth 1.0a. /// </summary> - internal string ParameterPrefix { - get { return this.parameterPrefix; } - } + internal Version Version { get; private set; } /// <summary> - /// Gets the scheme to use in Authorization header message requests. + /// Gets the namespace to use for this version of the protocol. /// </summary> - internal string AuthorizationHeaderScheme { - get { return this.authorizationHeaderScheme; } + internal string DataContractNamespace { + get { return this.dataContractNamespace; } } /// <summary> - /// Gets an instance of <see cref="Protocol"/> given a <see cref="Version"/>. + /// Gets the OAuth Protocol instance to use for the given version. /// </summary> - /// <param name="version">The version of the protocol that is desired.</param> - /// <returns>The <see cref="Protocol"/> instance representing the requested version.</returns> - internal static Protocol Lookup(Version version) { - switch (version.Major) { - case 1: return Protocol.V10; + /// <param name="version">The OAuth version to get.</param> + /// <returns>A matching <see cref="Protocol"/> instance.</returns> + public static Protocol Lookup(ProtocolVersion version) { + switch (version) { + case ProtocolVersion.V10: return Protocol.V10; + case ProtocolVersion.V10a: return Protocol.V10a; default: throw new ArgumentOutOfRangeException("version"); } } + + /// <summary> + /// Gets the OAuth Protocol instance to use for the given version. + /// </summary> + /// <param name="version">The OAuth version to get.</param> + /// <returns>A matching <see cref="Protocol"/> instance.</returns> + internal static Protocol Lookup(Version version) { + ErrorUtilities.VerifyArgumentNotNull(version, "version"); + Protocol protocol = AllVersions.FirstOrDefault(p => p.Version == version); + ErrorUtilities.VerifyArgumentInRange(protocol != null, "version"); + return protocol; + } } } diff --git a/src/DotNetOpenAuth/OAuth/ServiceProvider.cs b/src/DotNetOpenAuth/OAuth/ServiceProvider.cs index 345c6a2..38b7a60 100644 --- a/src/DotNetOpenAuth/OAuth/ServiceProvider.cs +++ b/src/DotNetOpenAuth/OAuth/ServiceProvider.cs @@ -48,7 +48,7 @@ namespace DotNetOpenAuth.OAuth { /// <param name="serviceDescription">The endpoints and behavior on the Service Provider.</param> /// <param name="tokenManager">The host's method of storing and recalling tokens and secrets.</param> /// <param name="messageTypeProvider">An object that can figure out what type of message is being received for deserialization.</param> - public ServiceProvider(ServiceProviderDescription serviceDescription, ITokenManager tokenManager, OAuthServiceProviderMessageFactory messageTypeProvider) { + public ServiceProvider(ServiceProviderDescription serviceDescription, IServiceProviderTokenManager tokenManager, OAuthServiceProviderMessageFactory messageTypeProvider) { ErrorUtilities.VerifyArgumentNotNull(serviceDescription, "serviceDescription"); ErrorUtilities.VerifyArgumentNotNull(tokenManager, "tokenManager"); ErrorUtilities.VerifyArgumentNotNull(messageTypeProvider, "messageTypeProvider"); @@ -156,13 +156,11 @@ namespace DotNetOpenAuth.OAuth { /// <param name="request">The token request message the Consumer sent that the Service Provider is now responding to.</param> /// <returns>The response message to send using the <see cref="Channel"/>, after optionally adding extra data to it.</returns> public UnauthorizedTokenResponse PrepareUnauthorizedTokenMessage(UnauthorizedTokenRequest request) { - if (request == null) { - throw new ArgumentNullException("request"); - } + ErrorUtilities.VerifyArgumentNotNull(request, "request"); string token = this.TokenGenerator.GenerateRequestToken(request.ConsumerKey); string secret = this.TokenGenerator.GenerateSecret(); - UnauthorizedTokenResponse response = new UnauthorizedTokenResponse(request, token, secret); + UnauthorizedTokenResponse response = new UnauthorizedTokenResponse(request, token, secret, Protocol.Default.Version); return response; } @@ -231,7 +229,7 @@ namespace DotNetOpenAuth.OAuth { ErrorUtilities.VerifyArgumentNotNull(request, "request"); ErrorUtilities.VerifyArgumentNotNull(callback, "callback"); - var authorization = new UserAuthorizationResponse(request.Callback) { + var authorization = new UserAuthorizationResponse(request.Callback, Protocol.Default.Version) { RequestToken = request.RequestToken, }; return authorization; @@ -282,7 +280,7 @@ namespace DotNetOpenAuth.OAuth { string accessToken = this.TokenGenerator.GenerateAccessToken(request.ConsumerKey); string tokenSecret = this.TokenGenerator.GenerateSecret(); this.TokenManager.ExpireRequestTokenAndStoreNewAccessToken(request.ConsumerKey, request.RequestToken, accessToken, tokenSecret); - var grantAccess = new AuthorizedTokenResponse(request) { + var grantAccess = new AuthorizedTokenResponse(request, Protocol.Default.Version) { AccessToken = accessToken, TokenSecret = tokenSecret, }; diff --git a/src/DotNetOpenAuth/OAuth/ServiceProviderDescription.cs b/src/DotNetOpenAuth/OAuth/ServiceProviderDescription.cs index 4636829..bdfd10d 100644 --- a/src/DotNetOpenAuth/OAuth/ServiceProviderDescription.cs +++ b/src/DotNetOpenAuth/OAuth/ServiceProviderDescription.cs @@ -26,9 +26,15 @@ namespace DotNetOpenAuth.OAuth { /// Initializes a new instance of the <see cref="ServiceProviderDescription"/> class. /// </summary> public ServiceProviderDescription() { + this.Version = Protocol.Default.Version; } /// <summary> + /// Gets or sets the OAuth version supported by the Service Provider. + /// </summary> + public Version Version { get; set; } + + /// <summary> /// Gets or sets the URL used to obtain an unauthorized Request Token, /// described in Section 6.1 (Obtaining an Unauthorized Request Token). /// </summary> @@ -43,7 +49,7 @@ namespace DotNetOpenAuth.OAuth { } set { - if (value != null && UriUtil.QueryStringContainPrefixedParameters(value.Location, OAuth.Protocol.V10.ParameterPrefix)) { + if (value != null && UriUtil.QueryStringContainPrefixedParameters(value.Location, OAuth.Protocol.ParameterPrefix)) { throw new ArgumentException(OAuthStrings.RequestUrlMustNotHaveOAuthParameters); } diff --git a/src/DotNetOpenAuth/OAuth/WebConsumer.cs b/src/DotNetOpenAuth/OAuth/WebConsumer.cs index bbf6115..7de73c7 100644 --- a/src/DotNetOpenAuth/OAuth/WebConsumer.cs +++ b/src/DotNetOpenAuth/OAuth/WebConsumer.cs @@ -39,7 +39,7 @@ namespace DotNetOpenAuth.OAuth { /// Requires HttpContext.Current. /// </remarks> public UserAuthorizationRequest PrepareRequestUserAuthorization() { - Uri callback = this.Channel.GetRequestFromContext().UrlBeforeRewriting.StripQueryArgumentsWithPrefix(Protocol.Default.ParameterPrefix); + Uri callback = this.Channel.GetRequestFromContext().UrlBeforeRewriting.StripQueryArgumentsWithPrefix(Protocol.ParameterPrefix); return this.PrepareRequestUserAuthorization(callback, null, null); } diff --git a/src/DotNetOpenAuth/OpenId/Provider/ProviderEndpoint.cs b/src/DotNetOpenAuth/OpenId/Provider/ProviderEndpoint.cs index 37da8a3..635a3c0 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/ProviderEndpoint.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/ProviderEndpoint.cs @@ -102,7 +102,7 @@ namespace DotNetOpenAuth.OpenId.Provider { public static void SendResponse() { var pendingRequest = PendingAuthenticationRequest; PendingAuthenticationRequest = null; - Provider.SendResponse(pendingRequest); + Provider.SendResponse(pendingRequest); } /// <summary> |