Basic Authentication Woes

800 views
Skip to first unread message

Chuck Benedict

unread,
Jan 22, 2012, 8:04:19 AM1/22/12
to ServiceStack .NET Open Source REST Web Services Framework
I have used the latest trunk to try and wire up basic authentication
with the newly rolled authentication bits. Having trouble getting it
working, probably because I do not understand how it is supposed to
work. Can you answer a few questions for me?

What are the two types of cookies for (ss-id and ss-pid)?

When I call "/auth/basic", I get back a ss-id cookie that is pathed to
"/auth". Then when I try another API call, the original ss-id cookie
is not found, thus causing another authentication event. I suspect it
is because I overrode BasicAuthProvider::Authenticate. I did this
because I want a browser to be presented with a login dialog (for
casual testing) instead of being thrown an exception if credentials
are not sent (which practically speaking cannot be done unless I
create a custom page to do so...but why when the browser will happily
do so).

My override looks like:

public override object Authenticate(IServiceBase authService,
IAuthSession session, Auth request)
{
var httpReq =
authService.RequestContext.Get<IHttpRequest>();
var basicAuth = httpReq.GetBasicAuthUserAndPassword();
// Overridden...present login dialog
//if (basicAuth == null)
// throw HttpError.Unauthorized("Invalid BasicAuth
credentials");
if (basicAuth == null) {
this.AuthRealm =
authService.RequestContext.Get<IHttpRequest>().QueryString["realm"];
if (this.AuthRealm == null)
throw HttpError.Unauthorized("Requested realm is
missing. Please add ?realm=<database> to your call.");
this.AuthRealm = "/auth/basic/" + this.AuthRealm;
var httpRes =
authService.RequestContext.Get<IHttpResponse>();
HttpResponseExtensions.ReturnAuthRequired(httpRes,
this.AuthRealm);
return null;
}

var userName = basicAuth.Value.Key;
var password = basicAuth.Value.Value;

return Authenticate(authService, session, userName,
password);
}

My question is two fold...to improve my own understanding, what is the
purpose of the two cookies, ss-id and ss-pid? And two, shouldn't the
ss-id be pathed to root and if so, why is this code not?

Chuck Benedict

unread,
Jan 22, 2012, 8:46:47 AM1/22/12
to ServiceStack .NET Open Source REST Web Services Framework
Followup...when I add the root path to the following call in
Cookies.cs (the AddPermanentCookie method already has it) like so,
basic auth is now working for me, with the browser login dialog per my
code in original post. What is really strange is that I looked at the
cookie setups on the response and the temporary cookie path was set to
empty. There must be some magic within the httpresponse chain that is
changing this based on the path of the request???

/// <summary>
/// Sets a session cookie which expires after the browser session
closes
/// </summary>
public void AddSessionCookie(string cookieName, string cookieValue)
{
// CCB Hack!
AddCookie(new Cookie(cookieName, cookieValue, RootPath));

Chuck Benedict

unread,
Jan 22, 2012, 10:32:40 AM1/22/12
to ServiceStack .NET Open Source REST Web Services Framework
One final change I made and so not sure if the framework can be
altered to accommodate change so I don't have to hack it...

Because I want the API user to specify the "realm" into which they
will be accessing my api (really this is a database internally), if
they were to just call one of my API methods without first calling /
auth/basic, instead of the default behavior which is to present the
login dialog (which is funny because this is what I added to the
BasicAuthProvider), I want the user to be informed to call "/auth/
basic" with the proper realm identifier. To accomplish, I changed the
code in AuthenticateAttribute.cs, Execute method like so:

public override void Execute(IHttpRequest req, IHttpResponse res,
object requestDto)
{
if (AuthService.AuthProviders == null) throw new
InvalidOperationException("The AuthService must be initialized by
calling "
+ "AuthService.Init to use an authenticate attribute");

var matchingOAuthConfigs = AuthService.AuthProviders.Where(x =>
this.Provider.IsNullOrEmpty()
|| x.Provider == this.Provider).ToList();

if (matchingOAuthConfigs.Count == 0)
{
res.WriteError(req, requestDto, "No OAuth Configs found matching
{0} provider"
.Fmt(this.Provider ?? "any"));
res.Close();
return;
}

var userPass = req.GetBasicAuthUserAndPassword();
if (userPass != null)
{
var authService = req.TryResolve<AuthService>();
authService.RequestContext = new HttpRequestContext(req, res,
requestDto);
var response = authService.Post(new Auth.Auth {
provider = BasicAuthProvider.Name,
UserName = userPass.Value.Key,
Password = userPass.Value.Value
});
}

using (var cache = req.GetCacheClient())
{
var sessionId = req.GetSessionId();
var session = sessionId != null ? cache.GetSession(sessionId) :
null;

if (session == null || !matchingOAuthConfigs.Any(x =>
session.IsAuthorized(x.Provider)))
{
// CCB HACK!
throw HttpError.Unauthorized("Not authenticated.
Please call /auth/ebsi/?realm=<database> first.");
// res.StatusCode = (int)HttpStatusCode.Unauthorized;
// res.AddHeader(HttpHeaders.WwwAuthenticate, "{0} realm=\"{1}\""
// .Fmt(matchingOAuthConfigs[0].Provider,
matchingOAuthConfigs[0].AuthRealm));

// res.Close();

Demis Bellot

unread,
Jan 22, 2012, 11:40:10 AM1/22/12
to servic...@googlegroups.com
What are the two types of cookies for (ss-id and ss-pid)? 

ss-id is a Session (aka temporary) cookie while ss-pid is a permanent cookie.
There is also another cookie called ss-opt which at the moment stores values 'temp' or 'perm' to indicate against which Id you want to store the User's Session against.
You can control which one gets used with the 'RememberMe' flag when you Register the user.

The Session cookie (ss-id) only lasts as long as the browser window is open, which has the effect of only 'remembering' the user until they close the browser.

The CookieContainer In C# ServiceClients only retain permanent cookies (i.e. ss-pid) so in order for the client to 'maintain' a session it needs to use permanent cookies, i.e. the 'RememberMe=true' when registering the user.

And two, shouldn't the ss-id be pathed to root and if so, why is this code not?

Both should be pathed to / - in my re-factoring of Cookie handling I missed out the RootPath - it's there now (in next release later today)

Demis Bellot

unread,
Jan 22, 2012, 11:40:46 AM1/22/12
to servic...@googlegroups.com
Yep, I've fixed this now in my trunk as well.

Demis Bellot

unread,
Jan 22, 2012, 12:35:51 PM1/22/12
to servic...@googlegroups.com
Well it's preferable to get your client look at the WWW-Authenticate header instead.
You have freedom to choose what AuthRealm in your AuthProvider is.
Ideally you should get your clients to inspect this header to determine how to authenticate.

Although let me know why this doesn't work for you as I can also provide an option to do a redirect if it helps?

Cheers,

Demis Bellot

unread,
Jan 22, 2012, 12:53:55 PM1/22/12
to servic...@googlegroups.com
Well it's preferable to get your client look at the WWW-Authenticate header instead.
Also you have freedom to choose what AuthRealm in your AuthProvider is, if you prefer I can include an option that redirects instead?

Generally features need to be generically useful / applicable to everyone to make it in ServiceStack, so if you do have features that are generally useful send in a pull request and I can bake it in.

Cheers,

On Sun, Jan 22, 2012 at 10:32 AM, Chuck Benedict <chuck.c....@gmail.com> wrote:

chuck_b...@yahoo.com

unread,
Jan 22, 2012, 1:32:12 PM1/22/12
to servic...@googlegroups.com
The trick is that I do not know ahead of time which realm to challenge the user with.  I want the user to request which realm they want to authenticate into first.  And so I am doing that by tacking on a parameter to the /auth/basic call, and then setting the AuthRealm in my own BasicAuthProvider class.  Thus why the callback within AuthenticateAttribute.Execute providing a blank realm is not helpful.  I realize this may be unconventional and if I can think of a way to make it generic, I will fork and request a pull.  If you have another idea on how to leverage the framework to accomplish what I am trying to do without the hack...I'm all ears.  By the way, kudos for the great work.  I beat my head against the wall for a week with WCF and got something working with ServceStack in 2 days.

Chuck Benedict

Planning a Hawaii vacation?
www.hanaleibayresortcondo.com

--- On Sun, 1/22/12, Demis Bellot <demis....@gmail.com> wrote:

Demis Bellot

unread,
Jan 22, 2012, 1:59:21 PM1/22/12
to servic...@googlegroups.com
Hi,

I've added a OnFailedAuthentication hook so AuthProviders can customize the way the response is handled:

Let me know if this works for you.

Cheers,

Chuck Benedict

unread,
Jan 22, 2012, 2:23:57 PM1/22/12
to ServiceStack .NET Open Source REST Web Services Framework
Sweeeeet. That is perfect! Will pull the latest trunk and try out.

Many thanks,
Chuck Benedict

On Jan 22, 12:59 pm, Demis Bellot <demis.bel...@gmail.com> wrote:
> Hi,
>
> I've added a OnFailedAuthentication hook so AuthProviders can customize the
> way the response is handled:https://github.com/ServiceStack/ServiceStack/commit/43930ae4ac38a297a...
>
> Let me know if this works for you.
>
> Cheers,
>
>
>
>
>
>
>
>
>
> On Sun, Jan 22, 2012 at 1:32 PM, <chuck_bened...@yahoo.com> wrote:
> > The trick is that I do not know ahead of time which realm to challenge the
> > user with.  I want the user to request which realm they want to
> > authenticate into first.  And so I am doing that by tacking on a parameter
> > to the /auth/basic call, and then setting the AuthRealm in my own
> > BasicAuthProvider class.  Thus why the callback within
> > AuthenticateAttribute.Execute providing a blank realm is not helpful.  I
> > realize this may be unconventional and if I can think of a way to make it
> > generic, I will fork and request a pull.  If you have another idea on how
> > to leverage the framework to accomplish what I am trying to do without the
> > hack...I'm all ears.  By the way, kudos for the great work.  I beat my head
> > against the wall for a week with WCF and got something working with
> > ServceStack in 2 days.
>
> > Chuck Benedict
>
> > Planning a Hawaii vacation?
> >www.hanaleibayresortcondo.com
>
> > --- On *Sun, 1/22/12, Demis Bellot <demis.bel...@gmail.com>* wrote:
>
> > From: Demis Bellot <demis.bel...@gmail.com>
> > Subject: Re: [ServiceStack] Re: Basic Authentication Woes
> > To: servic...@googlegroups.com
> > Date: Sunday, January 22, 2012, 11:53 AM
>
> > Well it's preferable to get your client look at the WWW-Authenticate
> > header instead.
> > Also you have freedom to choose what AuthRealm in your AuthProvider is, if
> > you prefer I can include an option that redirects instead?
>
> > Generally features need to be generically useful / applicable to everyone
> > to make it in ServiceStack, so if you do have features that are generally
> > useful send in a pull request and I can bake it in.
>
> > Cheers,
>
> > On Sun, Jan 22, 2012 at 10:32 AM, Chuck Benedict <
> > chuck.c.bened...@gmail.com<http://mc/compose?to=chuck.c.bened...@gmail.com>
> > On Jan 22, 7:46 am, Chuck Benedict <chuck.c.bened...@gmail.com<http://mc/compose?to=chuck.c.bened...@gmail.com>>
> > wrote:
> > > Followup...when I add the root path to the following call in
> > > Cookies.cs (the AddPermanentCookie method already has it) like so,
> > > basic auth is now working for me, with the browser login dialog per my
> > > code in original post.  What is really strange is that I looked at the
> > > cookie setups on the response and the temporary cookie path was set to
> > > empty.  There must be some magic within the httpresponse chain that is
> > > changing this based on the path of the request???
>
> > >                 /// <summary>
> > >                 /// Sets a session cookie which expires after the
> > browser session
> > > closes
> > >                 /// </summary>
> > >                 public void AddSessionCookie(string cookieName, string
> > cookieValue)
> > >                 {
> > >                         // CCB Hack!
> > >                         AddCookie(new Cookie(cookieName, cookieValue,
> > RootPath));
> > >                 }
>
> > > On Jan 22, 7:04 am, Chuck Benedict <chuck.c.bened...@gmail.com<http://mc/compose?to=chuck.c.bened...@gmail.com>>

Demis Bellot

unread,
Jan 22, 2012, 5:23:40 PM1/22/12
to servic...@googlegroups.com
FYI: this change is now in the latest version of NuGet.
Reply all
Reply to author
Forward
0 new messages