We have a web farm scenario that looks like this:
1) We have our OpenId Provider in a load-balanced environment, running
on several Windows servers.
2) The user visits a page at our RP and is redirected to one of the
load balanced OP servers for authentication.
3) The actual authentication can be provided by Google, which means
the user is redirected off of the OP server and to Google for
authentication.
4) The user is redirected back from Google to our OP pool of servers,
and here is where the fun begins:
If the user hits the same OP server as she was redirected to Google
from, there will be a ProviderEndpoint.PendingAuthenticationRequest
waiting for her when she returns (the pending request will be the one
that originated from the initial request made by the RP).
But what if she lands on one of the other OP servers, that has no
PendingRequest waiting for her?
I have implemented SQL Server Mode session state at the OP servers, so
I should be safe when it comes to session handling and sharing in
general, but it all boils down to this property of the
ProviderEndpoint class:
/// <summary>
/// Gets or sets an incoming OpenID authentication request that has
not yet been responded to.
/// </summary>
/// <remarks>
/// This request is stored in the ASP.NET Session state, so it will
survive across
/// redirects, postbacks, and transfers. This allows you to
authenticate the user
/// yourself, and confirm his/her desire to authenticate to the
relying party site
/// before responding to the relying party's authentication request.
/// </remarks>
public static IAuthenticationRequest PendingAuthenticationRequest {
get { return HttpContext.Current.Session[PendingRequestKey] as
IAuthenticationRequest; }
set { HttpContext.Current.Session[PendingRequestKey] = value; }
}
Does the fact that the PendingAuthenticationRequest is stored in
session, and therefore in SQL Server, mean that it can be restored
back from state at *any one* of the OP servers, and used to generate a
response back to the calling RP?
Can you see any (other) signs of this scenario violating the intended
use of the DNOA library?
To sum up our scenario:
RP (dnoa) => OP (dnoa) => Google
Regards,
Terje
Terje
To unsubscribe from this group, send email to dotnetopenid+unsubscribegooglegroups.com or reply to this email with the words "REMOVE ME" as the subject.
On Mar 23, 2:47 pm, Andrew Arnott <andrewarn...@gmail.com> wrote:
> Ah, yes. I suppose just ignoring the member doesn't prevent the session
> state from being stored, which may be undesirable if you're not using it and
> it's taking up space on your session server. I should have made that a
> property on the control that can be turned off.
Can you please elaborate on what you mean when you say "if you're not
using it" (it=ProviderEndpoint.PendingAuthenticationRequest) ?
My guess is that you mean I don't use it anymore after storing it
somewhere else, like in my one state bag in session (=sql server)
Is that right? Because, afaik, I have to use the request/ pick it up
somewhere, to be able to create a response back to the RP, after being
redirected back to the OP from Google, right?
> 2010/3/23 Øyvind Sean Kinsey <oyv...@kinsey.no>
>
> > I was actually referring to override the methods currently using this
> > member, and not the member itself, but I guess I should have made this more
> > clear :)
The methods currently using this member
(=PendingAuthenticationRequest) are in my own classes at the OP, just
like the member is being used in the OpenIdProviderWebForms samples
(e.g. in Page_Load in decide.aspx.cs)
So I don't quite get what you mean when you say "override the methods
currently using this member".
Can you please elaborate on this? Did you mean storing the
pendingauthrequest in a state bag?
My goal is to be able to create a response back to the RP, on whatever
of the OP servers in the pool that's being called upon when redirected
back from Google, without loosing the context/state of the initial
request.
> >> What you must also do is implement the RP and OP application stores.
> >> Since you already have a database this should be straightforward. Just
> >> implement the IRelyingPartyApplicationStore and IProviderApplicationStore
> >> interfaces and pass these objects respectively into the OpenIdRelyingParty
> >> and the OpenIdProvider classes (which you can do via the web.config file if
> >> you prefer). Technically you could just write a stateful Provider and leave
> >> the RP in stateless mode, but since you're all on the same set of hardware
> >> and database, it seems a shame to not go the rest of the way and make the
> >> whole thing stateful.
I'm planning over time to implement the RP and OP application stores.
But for now, I was planning to let both the RP send stateless auth
requests to the OP, and let the OP (functioning also as an RP
remember) send stateless auth requests to Google.
Does that make sense, and do you see any problems with that in a LB
environment (until I get the app stores done)?
Regards,
Terje
> Ah, yes. I suppose just ignoring the member doesn't prevent the sessionCan you please elaborate on what you mean when you say "if you're not
> state from being stored, which may be undesirable if you're not using it and
> it's taking up space on your session server. I should have made that a
> property on the control that can be turned off.
using it" (it=ProviderEndpoint.PendingAuthenticationRequest) ?
My guess is that you mean I don't use it anymore after storing it
somewhere else, like in my one state bag in session (=sql server)
Is that right? Because, afaik, I have to use the request/ pick it up
somewhere, to be able to create a response back to the RP, after being
redirected back to the OP from Google, right?
> 2010/3/23 Øyvind Sean Kinsey <oyv...@kinsey.no>
>(=PendingAuthenticationRequest) are in my own classes at the OP, just
> > I was actually referring to override the methods currently using this
> > member, and not the member itself, but I guess I should have made this more
> > clear :)
The methods currently using this member
like the member is being used in the OpenIdProviderWebForms samples
(e.g. in Page_Load in decide.aspx.cs)
So I don't quite get what you mean when you say "override the methods
currently using this member".
Can you please elaborate on this? Did you mean storing the
pendingauthrequest in a state bag?
My goal is to be able to create a response back to the RP, on whatever
of the OP servers in the pool that's being called upon when redirected
back from Google, without loosing the context/state of the initial
request.
I'm planning over time to implement the RP and OP application stores.
> >> What you must also do is implement the RP and OP application stores.
> >> Since you already have a database this should be straightforward. Just
> >> implement the IRelyingPartyApplicationStore and IProviderApplicationStore
> >> interfaces and pass these objects respectively into the OpenIdRelyingParty
> >> and the OpenIdProvider classes (which you can do via the web.config file if
> >> you prefer). Technically you could just write a stateful Provider and leave
> >> the RP in stateless mode, but since you're all on the same set of hardware
> >> and database, it seems a shame to not go the rest of the way and make the
> >> whole thing stateful.
But for now, I was planning to let both the RP send stateless auth
requests to the OP, and let the OP (functioning also as an RP
remember) send stateless auth requests to Google.
Does that make sense, and do you see any problems with that in a LB
environment (until I get the app stores done)?
Ok, I see that I'm getting confused here, because the mix of tasks and
state stores in my OP, as it also function as an RP...
Can we try to sum up?
1) Because the OP is a web farm, the
ProviderEnpoint.PendingAuthenticationRequest is safe across the farm
because of SQL Session Store..
2) But the RP part of the OP is *not* safe in its handling of
application stores before I create a database store for them aswell?
3) Our website functioning as a pure RP (also a web farm) is safe,
because it uses stateless communication with our OP farm, right?
Hmm, looking at my three statements I see that it's really confusing..
Regarding the application stores: In what situations are they
required? You say they are required for the OP to function in a web
farm. So far so good. But what about our pure RP which is also web-
farmed? Does that also need application stores, or is stateless
enough?
Do you happen to have a demo-implementation of the OP application
store to offer me? (I guess I would need to come up with the tables,
the stored procedures, and the ado.net logic to move data back and
forth, meaning a lot of writing code and testing, so a demo-app would
be most helpful :)
Regards,
Terje
> The OpenIdProvider MUST have a state store in its function as a Provider.Ok, I see that I'm getting confused here, because the mix of tasks and
> You may be "getting away with it" currently because it has a default
> server-level application store implementation. But in a web farm
> environment this is insufficient and will lead to erratic failures. Your
> use of OpenIdRelyingParty can be stateless though.
state stores in my OP, as it also function as an RP...
Can we try to sum up?
1) Because the OP is a web farm, the
ProviderEndpoint.PendingAuthenticationRequest is safe across the farm
because of SQL Session Store..
2) But the RP part of the OP is *not* safe in its handling of
application stores before I create a database store for them aswell?
3) Our website functioning as a pure RP (also a web farm) is safe,
because it uses stateless communication with our OP farm, right?
Hmm, looking at my three statements I see that it's really confusing..
Regarding the application stores: In what situations are they
required? You say they are required for the OP to function in a web
farm. So far so good. But what about our pure RP which is also web-
farmed? Does that also need application stores, or is stateless
enough?
Do you happen to have a demo-implementation of the OP application
store to offer me? (I guess I would need to come up with the tables,
the stored procedures, and the ado.net logic to move data back and
forth, meaning a lot of writing code and testing, so a demo-app would
be most helpful :)
Thanks very much!
Regards,
Terje
On Mar 23, 10:55 pm, Andrew Arnott <andrewarn...@gmail.com> wrote:
> On Tue, Mar 23, 2010 at 12:32 PM, terjeu <terje.ulves...@gmail.com> wrote:
> > > The OpenIdProvider MUST have a state store in its function as a Provider.
> > > You may be "getting away with it" currently because it has a default
> > > server-level application store implementation. But in a web farm
> > > environment this is insufficient and will lead to erratic failures. Your
> > > use of OpenIdRelyingParty can be stateless though.
>
> > Ok, I see that I'm getting confused here, because the mix of tasks and
> > state stores in my OP, as it also function as an RP...
> > Can we try to sum up?
> > 1) Because the OP is a web farm, the
> > ProviderEndpoint.PendingAuthenticationRequest is safe across the farm
> > because of SQL Session Store..
>
> That's right. But there is more state required for Providers than just for
> ProviderEndpoint.PendingAuthenticationRequest. That one property uses *
> session* state, so that the individual authentication request is associated
> with an individual user across HTTP requests. But there is an even more
> important bag of state that all Providers must support: *application* state,
> which stores association secrets and consumed nonces. This application
> state *can* be just in server memory if there's just one server. But when
> the Provider is on a web farm, the application state must be in a shared
> database as well. DNOA accesses this database through the
> IProviderApplicationState interface which you must pass to the
> OpenIdProvider constructor, or through the web.config file.
>
> > 2) But the RP part of the OP is *not* safe in its handling of
> > application stores before I create a database store for them aswell?
>
> RPs *may* not have any state at all. This is true whether it's a pure RP or
> if its the RP role in an RP/OP hybrid. In DotNetOpenAuth state*less* mode
> is done with "new OpenIdRelyingParty(*null*)", or setting an
> ASP.NETcontrol's Stateless property to true. Note that passing null
> to the
> constructor is necessary -- calling the default constructor with no
> arguments is *not* stateless mode.
>
> RPs perform better and are more secure when they operate in state*ful* mode,
> store<http://github.com/aarnott/dotnetopenid/blob/v3.4/samples/OpenIdRelyin...>and
> associated ADO.NET
> schema<http://github.com/aarnott/dotnetopenid/blob/v3.4/samples/OpenIdRelyin...>.
> And a custom OP
> store<http://github.com/aarnott/dotnetopenid/blob/v3.4/samples/OpenIdProvid...>with
> associated ADO.NET
> schema<http://github.com/aarnott/dotnetopenid/blob/v3.4/samples/OpenIdProvid...>.
Association.Deserialize(bag.Handle, bag.Expires.ToUniversalTime(),
bag.Privatedata);
The error msg is:
[ArgumentOutOfRangeException: Specified argument was out of the range
of valid values.]
System.Diagnostics.Contracts.__ContractsRuntime.Requires(Boolean
condition, String message, String conditionText) +301
DotNetOpenAuth.OpenId.HmacShaAssociation..ctor(HmacSha
typeIdentity, String handle, Byte[] secret, TimeSpan totalLifeLength)
+184
DotNetOpenAuth.OpenId.HmacShaAssociation.Create(String handle,
Byte[] secret, TimeSpan totalLifeLength) +431
DotNetOpenAuth.OpenId.Association.Deserialize(String handle,
DateTime expiresUtc, Byte[] privateData) +308
[ArgumentException: The private data supplied does not meet the
requirements of any known Association type. Its length may be too
short, or it may have been corrupted.
Parameter name: privateData]
DotNetOpenAuth.OpenId.Association.Deserialize(String handle,
DateTime expiresUtc, Byte[] privateData) +365
DotNetOpenAuth.NRK.CustomProviderApplicationStore.GetAssociation(AssociationRelyingPartyType
distinguishingFactor, SecuritySettings securitySettings) in E:\Need\NRK
\NRKInnlogging\Main\Trunk\Source\OpenID src\DotNetOpenAuth.NRK
\CustomProviderApplicationStore.cs:52
DotNetOpenAuth.OpenId.ChannelElements.SigningBindingElement.GetDumbAssociationForSigning()
+30
DotNetOpenAuth.OpenId.ChannelElements.SigningBindingElement.GetAssociation(ITamperResistantOpenIdMessage
signedMessage) +383
DotNetOpenAuth.OpenId.ChannelElements.SigningBindingElement.ProcessOutgoingMessage(IProtocolMessage
message) +277
DotNetOpenAuth.Messaging.Channel.ProcessOutgoingMessage(IProtocolMessage
message) +319
DotNetOpenAuth.Messaging.Channel.PrepareResponse(IProtocolMessage
message) +176
DotNetOpenAuth.Messaging.Channel.Send(IProtocolMessage message)
+211
DotNetOpenAuth.OpenId.Provider.OpenIdProvider.SendResponse(IRequest
request) +346
DotNetOpenAuth.OpenId.Provider.ProviderEndpoint.SendResponse() +34
NRKRP.decide.Page_Load(Object sender, EventArgs e) in E:\Need\NRK
\NRKInnlogging\Main\Trunk\Source\NRK OpenID\NRK RP\decide.aspx.cs:67
System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr fp,
Object o, Object t, EventArgs e) +14
System.Web.Util.CalliEventHandlerDelegateProxy.Callback(Object
sender, EventArgs e) +35
System.Web.UI.Control.OnLoad(EventArgs e) +99
System.Web.UI.Control.LoadRecursive() +50
System.Web.UI.Page.ProcessRequestMain(Boolean
includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
+627
It's obviously the privatedata that's causing the problems.
Here is my method for GetAssociation:
public AssociationBag GetAssociation(string distinguishingFactor)
{
DbCommand selectCommand = null;
selectCommand = _db.GetStoredProcCommand("GetOpenIDAssociation");
_db.AddInParameter(selectCommand, "distinguishingFactor",
DbType.String, distinguishingFactor);
string handle = null;
DateTime expires = new DateTime();
long retval; // The bytes returned from GetBytes.
long startIndex = 0; // The starting position in the BLOB output
int bufferSize = 64; // Size of the BLOB buffer.
byte[] privatedata = new byte[bufferSize]; // The BLOB byte[]
buffer to be filled by GetBytes.
using (DbConnection conn = _db.CreateConnection())
{
conn.Open();
selectCommand.Connection = conn;
using (IDataReader dataReader =
selectCommand.ExecuteReader(CommandBehavior.SequentialAccess)) {
while (dataReader.Read()) {
handle = dataReader.GetString(0);
expires = dataReader.GetDateTime(1);
// Reset the starting byte for the new BLOB.
startIndex = 0;
// Read the bytes into outbyte[] and retain the number of bytes
returned.
retval = dataReader.GetBytes(2, startIndex, privatedata, 0,
bufferSize);
}
}
}
return new AssociationBag
{
Expires = expires,
Handle = handle,
Privatedata = privatedata
};
}
I have noticed that you have a field in the [OpenIDAssociation] table
called [PrivateDataLength]. I'm not using that now, and I guess that
is causing the trouble.
Can you please show me how I should code the dataReader.GetBytes to
proper handle the privatedata returned, and the privatedatalength
field? (I'm pretty sure the problem lies there, agree?)
Regards,
Terje
It looks like I've fixed the private data length problem, but I have
run into another problem regarding association expiry date:
My CustomProviderApplicationStore implements
IProviderApplicationStore. I'm implementing the
ClearExpiredAssociations method, but that method is never called. This
results in the [OpenIDAssociation] table being filled with expired
associations which in turn gives us an ArgumentOutOfRangeException
from the following code statement in the HmacShaAssociation
constructor:
Contract.Requires<ArgumentOutOfRangeException>(totalLifeLength >
TimeSpan.Zero);
Can I relate this exception to the fact that ClearExpiredAssociations
has not been called?
When is it appropriate to call the ClearExpiredAssociations method?
Exception stack trace:
2010-03-25 15:04:52,989 (GMT+1) [6] ERROR NRKRP.Global - An unhandled
exception was raised. Details follow:
System.Web.HttpUnhandledException: Exception of type
'System.Web.HttpUnhandledException' was thrown. --->
System.ArgumentException: The private data supplied does not meet the
requirements of any known Association type. Its length may be too
short, or it may have been corrupted.
Parameter name: privateData ---> System.ArgumentOutOfRangeException:
Specified argument was out of the range of valid values.
at
System.Diagnostics.Contracts.__ContractsRuntime.Requires[TException]
(Boolean condition, String message, String conditionText)
at DotNetOpenAuth.OpenId.HmacShaAssociation..ctor(HmacSha
typeIdentity, String handle, Byte[] secret, TimeSpan totalLifeLength)
in c:\BuildAgent\work\7ab20c0d948e028f\src\DotNetOpenAuth\OpenId
\HmacShaAssociation.cs:line 74
at DotNetOpenAuth.OpenId.HmacShaAssociation.Create(String handle,
Byte[] secret, TimeSpan totalLifeLength) in c:\BuildAgent\work
\7ab20c0d948e028f\src\DotNetOpenAuth\OpenId\HmacShaAssociation.cs:line
125
at DotNetOpenAuth.OpenId.Association.Deserialize(String handle,
DateTime expiresUtc, Byte[] privateData) in c:\BuildAgent\work
\7ab20c0d948e028f\src\DotNetOpenAuth\OpenId\Association.cs:line 171
--- End of inner exception stack trace ---
at DotNetOpenAuth.OpenId.Association.Deserialize(String handle,
DateTime expiresUtc, Byte[] privateData) in c:\BuildAgent\work
\7ab20c0d948e028f\src\DotNetOpenAuth\OpenId\Association.cs:line 173
at
DotNetOpenAuth.NRK.CustomProviderApplicationStore.GetAssociation(AssociationRelyingPartyType
distinguishingFactor, SecuritySettings securitySettings) in E:\Need\NRK
\NRKInnlogging\Main\Trunk\Source\OpenID src\DotNetOpenAuth.NRK
\CustomProviderApplicationStore.cs:line 49
at
DotNetOpenAuth.OpenId.ChannelElements.SigningBindingElement.GetDumbAssociationForSigning()
in c:\BuildAgent\work\7ab20c0d948e028f\src\DotNetOpenAuth\OpenId
\ChannelElements\SigningBindingElement.cs:line 406
at
DotNetOpenAuth.OpenId.ChannelElements.SigningBindingElement.GetAssociation(ITamperResistantOpenIdMessage
signedMessage) in c:\BuildAgent\work\7ab20c0d948e028f\src
\DotNetOpenAuth\OpenId\ChannelElements\SigningBindingElement.cs:line
325
at
DotNetOpenAuth.OpenId.ChannelElements.SigningBindingElement.ProcessOutgoingMessage(IProtocolMessage
message) in c:\BuildAgent\work\7ab20c0d948e028f\src\DotNetOpenAuth
\OpenId\ChannelElements\SigningBindingElement.cs:line 103
at
DotNetOpenAuth.Messaging.Channel.ProcessOutgoingMessage(IProtocolMessage
message) in c:\BuildAgent\work\7ab20c0d948e028f\src\DotNetOpenAuth
\Messaging\Channel.cs:line 763
at
DotNetOpenAuth.Messaging.Channel.PrepareResponse(IProtocolMessage
message) in c:\BuildAgent\work\7ab20c0d948e028f\src\DotNetOpenAuth
\Messaging\Channel.cs:line 244
at DotNetOpenAuth.Messaging.Channel.Send(IProtocolMessage message)
in c:\BuildAgent\work\7ab20c0d948e028f\src\DotNetOpenAuth\Messaging
\Channel.cs:line 231
at
DotNetOpenAuth.OpenId.Provider.OpenIdProvider.SendResponse(IRequest
request) in c:\BuildAgent\work\7ab20c0d948e028f\src\DotNetOpenAuth
\OpenId\Provider\OpenIdProvider.cs:line 289
at DotNetOpenAuth.OpenId.Provider.ProviderEndpoint.SendResponse()
in c:\BuildAgent\work\7ab20c0d948e028f\src\DotNetOpenAuth\OpenId
\Provider\ProviderEndpoint.cs:line 139
at NRKRP.decide.Page_Load(Object sender, EventArgs e) in E:\Need\NRK
\NRKInnlogging\Main\Trunk\Source\NRK OpenID\NRK RP\decide.aspx.cs:line
67
at System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr fp,
Object o, Object t, EventArgs e)
at System.Web.Util.CalliEventHandlerDelegateProxy.Callback(Object
sender, EventArgs e)
at System.Web.UI.Control.OnLoad(EventArgs e)
at System.Web.UI.Control.LoadRecursive()
at System.Web.UI.Page.ProcessRequestMain(Boolean
includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
--- End of inner exception stack trace ---
at System.Web.UI.Page.HandleError(Exception e)
at System.Web.UI.Page.ProcessRequestMain(Boolean
includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
at System.Web.UI.Page.ProcessRequest(Boolean
includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
at System.Web.UI.Page.ProcessRequest()
at System.Web.UI.Page.ProcessRequestWithNoAssert(HttpContext
context)
at System.Web.UI.Page.ProcessRequest(HttpContext context)
at ASP.decide_aspx.ProcessRequest(HttpContext context)
at
System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step,
Boolean& completedSynchronously)
Regards,
Terje
--
You received this message because you are subscribed to the Google Groups "DotNetOpenAuth" group.
To post to this group, send email to dotnet...@googlegroups.com.
To unsubscribe from this group, send email to dotnetopenid...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/dotnetopenid?hl=en.
I have one stored procedure that handles
GetAssociation(AssociationRelyingPartyType distinguishingFactor,
SecuritySettings securitySettings), called GetOpenIDAssociation (shown
under).
And I have one procedure that handles
GetAssociation(AssociationRelyingPartyType distinguishingFactor,
string handle), called GetUniqueOpenIDAssociation (shown under).
The GetUniqueOpenIDAssociation is only returning the association if it
hasn't expired, whereas GetOpenIDAssociation will return every
association (with matching distinguishingFactor) with expires in
descending order (*without* concerning wheter it has expired). Does
that make sense, or should both check for expiration?
(Is this a valid check for expiration: Expiration > getutcdate() ?)
Can you please check my procedures and see if they do the right thing?
ALTER PROCEDURE [dbo].[GetOpenIDAssociation]
@distinguishingFactor varchar(255)
AS
BEGIN
SET NOCOUNT ON;
SELECT AssociationHandle AS Handle, Expiration AS Expires,
PrivateDataLength, PrivateData
FROM OpenIDAssociation
WHERE (DistinguishingFactor = @distinguishingFactor)
ORDER BY Expires DESC
END
ALTER PROCEDURE [dbo].[GetUniqueOpenIDAssociation]
@distinguishingFactor varchar(255),
@handle varchar(255)
AS
BEGIN
SET NOCOUNT ON;
SELECT AssociationHandle AS Handle, Expiration AS Expires,
PrivateDataLength, PrivateData
FROM OpenIDAssociation
WHERE (DistinguishingFactor = @distinguishingFactor) AND
(AssociationHandle = @handle)
AND (Expiration > getutcdate())
END
Regards,
Terje
Regards,
Terje
I wasn't aware of those comments, but I see that they answer my
questions.
Thanks for telling me! :)
Terje