CAS 6.2.X: TGC Cookie set twice

501 views
Skip to first unread message

Ulrich Mayring

unread,
Jan 7, 2021, 8:00:51 AM1/7/21
to CAS Community
Before posting a bug report I'd like to hear your opinions - perhaps I'm on the wrong page.

When authenticating with an application that uses CAS, the first thing that happens is that the CAS login form appears. Before that form is sent to the browser, CAS is internally trying to delete any TGC-Cookies that might exist.


The result is that the following header is sent to the browser (JSON notation as copied from the Firefox Dev Tools):

"name": "set-cookie",
"value": "TGC-1.2.3=\"\"; Version=1; Path=/mycas/; Secure; HttpOnly; Max-Age=0; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Comment=\"CAS Cookie\""

The reason for that "empty" cookie is probably that the only way to delete a cookie as per the Servlet API is to set a new one with an empty value. Apparently CAS is trying to do that here. It doesn't matter much, because we are talking only about the login page, but this becomes important later on.

When I login via CAS protocol, i.e . POSTing my credentials to https://cas-server/mycas/login?locale=de I'll get back a proper TGC cookie and am authenticated:

"name": "set-cookie",
"value": "TGC-1.2.3=eyJhbG...; Path=/mycas/; SameSite=None; Secure; HttpOnly"

However, when I do the same thing via external authentication (Azure OpenID Connect), I am entering the OAuth2 Authorization Code flow and arrive at POSTing to the URL https://cas-server/mycas/login?code=0.ATAAHe5... - in other words, I am not sending my credentials, but an Authorization Code I have obtained from Azure. Upon POSTing to that URL I am getting both of the above-mentioned headers back.

So the browser is getting the "full" TGC-Cookie and an "empty" one. Because the "empty" one arrives last, the browser sends it for its next request and obviously authentication fails at that point.

I have debugged into the CAS code and found that the above-mentioned code in line 95 of InitialFlowSetupAction is called again during authentication, which results in the extra "empty" header.

Bottom line: the TGC cookie is not removed properly, resulting in two "set-cookie" headers. But this only breaks external authentication. When authenticating directly with CAS the cookie removal code is not called during authentication and thus we only have one "set-cookie" header.

I have noted that the code to remove cookies was changed between CAS 5.3 (where this issue does not exist) to CAS 6.2. So what do you think? Is this a bug or do I misunderstand something about cookie handling or CAS?

Ray Bon

unread,
Jan 7, 2021, 1:22:14 PM1/7/21
to cas-...@apereo.org
Ulrich,

I see the two TGCs on return from azure. I can not tell which arrived first, but the stored TGC does have a value, and subsequent login does send this value.
Could this be related to browser behaviour?

I tested in firefox and chrome. The empty cookie has Expires=1970... and Max-Age=0; the one with a value has no Max-Age and no Expires but does have a SameSite.

Ray

On Thu, 2021-01-07 at 05:00 -0800, Ulrich Mayring wrote:
Notice: This message was sent from outside the University of Victoria email system. Please be cautious with links and sensitive information.
-- 
Ray Bon
Programmer Analyst
Development Services, University Systems

I respectfully acknowledge that my place of work is located within the ancestral, traditional and unceded territory of the Songhees, Esquimalt and WSÁNEĆ Nations.

Ulrich Mayring

unread,
Jan 8, 2021, 4:21:40 AM1/8/21
to CAS Community, Ray Bon
I have tested this with Firefox 84 and Chrome 87.0.4280.88 and in both cases no cookie is sent with the next request, thus failing to login the user.

As far as I understand, the server is allowed to send multiple "Set-Cookie" headers with different values. The client (browser), however, is only allowed to send one "Cookie" header back. He can concatenate the multiple values into that one field, though. But it appears that in my case the browser looks at the two Set-Cookie headers seperately. The first one is accepted as a new cookie, but the second one without a value is apparently interpreted as "delete this cookie". Thus no cookie is sent with the next request.

If I understand you correctly, then in your case the browser is sending the correct TGC-Cookie, even though you also receive the two Set-Cookie headers?

Kind regards,
Ulrich

Ray Bon

unread,
Jan 8, 2021, 12:29:19 PM1/8/21
to real...@gmail.com, cas-...@apereo.org
Ulrich,

Same versions of chrome and firefox on linux.
When I use delegated auth to azure, I first pass through the cas log in page and it redirects to azure. Thus my browser has already 'seen' the empty TGC.
Is this your flow, or do you go to azure first?

Also, does your TGC have a suffix, '-1.2.3'?
I am using the default cas setting that has no suffix, the cookie label is 'TGC'. This should not matter, but stranger things have happened.

Ray

Ulrich Mayring

unread,
Jan 8, 2021, 1:01:54 PM1/8/21
to CAS Community, Ray Bon, Ulrich Mayring
Different workflow here. I access my application and it redirects to the CAS Login Page. On the CAS Login Page I can choose whether to log in directly (via CAS protocol) or externally (via Azure). To that end there is a button that will take me to the Azure login page.

However, my browser will also have seen the "empty" cookie, since I'm passing through the CAS login page. But I don't think that can have any effect - what difference should it make to the browser, whether a cookie has been seen before? Have you checked in which order you receive the two cookies? Perhaps the "empty" cookie comes first in your case, so it is then overwritten by the "full" cookie?

My cookie is also named TGC-1.2.3, I'm using the CAS default as well.

cheers,
Ulrich

Ray Bon

unread,
Jan 8, 2021, 3:01:44 PM1/8/21
to cas-...@apereo.org, real...@gmail.com
Ulrich,

According to, https://tools.ietf.org/html/rfc6265, in particular 4.2.2, the order of cookies in the header should not matter.
Is it possible that the app server is setting/modifying the order? 
I am using tomcat 9.

Ray

Ulrich Mayring

unread,
Jan 9, 2021, 2:39:51 PM1/9/21
to CAS Community, Ray Bon, Ulrich Mayring
Ray,
the section you refer to applies to the "Cookie" header the browser sends back. However, I was talking about the "Set-Cookie" header sent by the server. Its semantics are defined in section 4.1.2 of the RFC and it clearly states that the server can delete a cookie by sending a Set-Cookie header with an expired date.

What's more, RFC 6265 clearly states that the server SHOULD NOT include more than one Set-Cookie header field in the same response with the same cookie-name. So CAS clearly is in violation of that. Most user-agents will probably react to that by accepting the last Set-Cookie header and so the order of those headers really matters.

Therefore my question to you was whether you are receiving the "Set-Cookie" headers in a different order than I am, because that would explain why your browser processes them differently.

Kind regards,
Ulrich

Ray Bon

unread,
Jan 11, 2021, 11:25:28 AM1/11/21
to real...@gmail.com, cas-...@apereo.org
Ulrich,

You are correct.
And I do receive the cookies with the expired cookie first.

Ray

Ulrich Mayring

unread,
Jan 11, 2021, 5:00:17 PM1/11/21
to CAS Community, Ray Bon, Ulrich Mayring
Ray,
thanks a lot for your comments. I believe this more or less settles the issue that we have a bug here, but it doesn't bite everyone. So I suppose those, who are unaffected, can just carry on.

We have fixed the issue in our overlay by simply removing the code that sets the "empty" cookie. If you ever find that the order of cookies changes on you, you can do the same thing. Our test suite is green, so I guess that for our purposes this rather brute-force fix will work. I have no idea what it would take to fix this in CAS main, but I did notice that the code to remove a cookie was changed from using the Servlet API to a custom implementation, where the header is set manually. So perhaps going back to using the Servlet API (response.addCookie) would be enough.

Pablo Vidaurri

unread,
Jan 3, 2024, 10:39:17 PM1/3/24
to CAS Community, Ulrich Mayring, Ray Bon
Ran into the same issue with v6.6.8 and v6.6.14. Also removed the line in InitialFlowSetupAction.java that sets empty cookie and I get proper session now. But this does not look like a correct fix.

Petr Bodnár

unread,
Mar 26, 2024, 2:40:57 PM3/26/24
to CAS Community, Pablo Vidaurri, Ulrich Mayring, Ray Bon
Hi all,

thanks for the fruitful discussion. Some more fresh remarks to the topic:

1) The order of "Set-Cookie" response headers indeed depends on the server

While Tomcat clearly uses deterministic, insertion-time order (see its Response.java), JBoss/WildFly for some weird reason stores cookies in a TreeSet, so their output order should be random (see its HttpServletResponseImpl.java and from there, HttpServerExchange.java).

2) Since version 6.4.0, CAS deletes TGC cookie more aggressively

In https://github.com/apereo/cas/commit/02e9c27a6b60505, a new method removeAll(request, response) was added (and is called) that additionally removes the cookie only if it is present in the request.

3) Since version v7.0.0-RC2, CAS deletes TGC only when it is present in the request

In https://github.com/apereo/cas/commit/9454b38b8d95d7, just the aforementioned new method removeAll(request, response) is used.

4) This issue doesn't affect just the delegated authentication, but also authentication via Kerberos (SPNEGO)

The Kerberos authentication seems to be affected in the very same way, but possibly no one really noticed or cares, because the automatic login is practically invisible to the user.

5) Conclusion

All that being said, it looks like removing the unconditional call to ticketGrantingTicketCookieGenerator.removeCookie(response) actually IS the correct solution. And leaving just the new call to removeAll(request, response) should be just fine - because this call should be effective only when being initially redirected to CAS login page with an invalid (e.g. expired) TGC cookie.

Feel free to correct me.

Regards
Petr

Pablo Vidaurri

unread,
Mar 27, 2024, 2:13:25 PM3/27/24
to CAS Community, Petr Bodnár, Pablo Vidaurri, Ulrich Mayring, Ray Bon
IPetr, you haven't seen any other issues with this work around? For my users authenticatng via delegated azure ad I'm getting a cas client not authorized error which is misleading because the issue in logs show a tgt primary key constraint violation. 

-psv

Petr Bodnár

unread,
Mar 30, 2024, 1:46:24 PM3/30/24
to CAS Community, Pablo Vidaurri, Petr Bodnár, Ulrich Mayring, Ray Bon
Hi Pablo,

I should have probably added that I haven't tested the proposed solutions yet. From what I've studied so far, I expected that commenting out just the call to removeCookie() should work.

Or is it so, that as you mention in the other thread, you had to comment out even the call to the whole clearTicketGrantingCookieFromContex()? I would guess that could possibly cause the "tgt primary key constraint violation" you write about (in the case of obtaining an expired TGC). But yes, this is just guessing from me now...

Petr

Pablo Vidaurri

unread,
Apr 4, 2024, 1:10:50 AM4/4/24
to CAS Community, Petr Bodnár, Pablo Vidaurri, Ulrich Mayring, Ray Bon
Hi Petr, 

It looks like I must have changed it after that post. I did not comment out the entire clearTicketGrantingCookieFromContext call, just commented out the line contained within it:
// ticketGrantingTicketCookieGenerator.removeCookie(response);

I've dedicated today to debugging this:

ERROR [org.hibernate.engine.jdbc.spi.SqlExceptionHelper] (default task-76) Batch entry 0 insert into Postgres_Jpa_Ticket_Entity (body, creation_Time, parent_Id, principal_Id, type, id) values ('{"@class":"org.apereo.cas.ticket.TransientSessionTicketImpl","@id":1,"expirationPolicy":{"@class":"org.apereo.cas.ticket.expiration.MultiTimeUseOrTimeoutExpirationPolicy$TransientSessionTicketExpirationPolicy","numberOfUses":3,"timeToLive":180,"name":"TransientSessionTicketExpirationPolicy-2547f234-9127-460d-9310-b1f901df3e40"},"id":"TST-xxxxxxx","lastTimeUsed":"2024-04-03T15:15:44.23861Z","creationTime":"2024-04-03T15:15:44.23861Z","properties":{"@class":"java.util.HashMap","AzureAdClient$stateSessionParameter":{"@class":"com.nimbusds.oauth2.sdk.id.State","value":"561f9c172a"}},"prefix":"TST"}', '2024-04-03 15:15:44.23861+00', NULL, '', 'org.apereo.cas.ticket.TransientSessionTicketImpl', 'TST-xxxxxxx') was aborted: ERROR: duplicate key value violates unique constraint "postgres_jpa_ticket_entity_pkey"
  Detail: Key (id)=(TST-xxxxxxx) already exists.  Call getNextException to see other errors in the batch.

ERROR [org.hibernate.engine.jdbc.spi.SqlExceptionHelper] (default task-76) ERROR: duplicate key value violates unique constraint "postgres_jpa_ticket_entity_pkey"
  Detail: Key (id)=(TST-xxxxxxx) already exists.

-psv
Reply all
Reply to author
Forward
0 new messages