[Discovery] 10 bí ẩn trong hệ Mặt Trời mà các nhà khoa học vẫn chưa thế lý giải

Mặc dù ngành khoa học vũ trụ đã có nhiều bước tiến mới, cùng với sự hỗ trợ của công nghệ tiên tiến nhưng vẫn còn đó những điều bí ẩn mà chúng ta chưa thể lý giải được. Không phải đâu xa mà ngay trong hệ Mặt Trời của chúng ta cũng vẫn có rất nhiều điều bí ẩn.

1. Tấm lá chắn vô hình xung quanh Trái đất

Năm 1958, James Van Allen tại đại học Iowa đã phát hiện một cặp vành đai bức xạ xung quanh Trái đất, ở khoảng cách 40.000km so với mặt đất. Các vành đai này chứa các hạt điện tử năng lượng cao và proton. Đây chính là các hạt năng lượng bắn ra từ hoạt động của Mặt Trời, vào những lúc Mặt Trời hoạt động mạnh hay còn gọi là hiện tượng bão Mặt Trời thì số lượng cũng như mức độ năng lượng của các hạt này càng tăng cao.

01

Các hạt năng lượng cao cùng bức xạ này chính là những kẻ giết người vô hình, không những vậy chúng còn có thể gây ảnh hưởng tới các vệ tinh và hệ thống điện tử trên Trái đất. Tuy nhiên hấu hết chúng đều không thể tiếp cận bề mặt Trái đất, một số hạt năng lượng cao hơn có thể xâm nhập sâu hơn bầu khí quyển, nhưng tất cả đều phải dừng lại ở khoảng cách 11.000km so với mặt đất.

Các nhà khoa học cho rằng có một bức tường vô hình giúp ngăn cản những hạt năng lượng này xâm nhập vào Trái đất. Nó giống như tấm lá chắn năng lượng vô hình trong các bộ phim viễn tưởng như Star Wars. Tuy nhiên vì sao Trái đất lại có một tấm lá chắn như vậy vẫn là điều bí ẩn chưa có lời giải đáp. Nhiều giả thuyết cho rằng từ trường của Trái đất là nguyên nhân các hạt năng lượng cao không thể xâm nhập vào bên trong, tuy nhiên giả thuyết này vẫn vấp phải nhiều ý kiến phản đối.

2. Hiện tượng bay ngang bất thường

Từ khi con người biết đưa các vệ tinh và tàu vũ trụ vào không gian, các nhà khoa học đã biết tận dụng lực hấp dẫn của các hành tinh giống nhu lực ly tâm để giúp tạo thêm lực đẩy cho những cuộc thám hiểm vũ trụ. Nó gần giống như khi bạn cột một sợi dây vào tảng đá, sau đó quay tròn để ném tảng đá đi xa hơn.

02

Tuy nhiên trong những lần áp dụng phương pháp này, các nhà khoa học nhận thấy một hiện tượng kỳ lạ. Đó là vận tốc quay của tàu vũ trụ hoặc vệ tinh xung quanh một hành tinh là không ổn định. Nó được gọi là hiện tượng bay ngang bất thường. Trong khi đó, trên lý thuyết thì vận tốc quay phải tăng dần hoặc giảm dần với gia tốc ổn định theo như tính toán của các nhà khoa học.

Còn trong thực tế thì tàu vũ trụ Cassini của NASA được phóng lên quỹ đạo của Trái đất vào năm 1999, lại có lúc tăng tốc và có lúc giảm tốc bất thường khi quay xung quanh Trái đất. Nhiều giả thuyết được đặt ra, trong đó có giả thuyết về vật chất tối còn xót lại trong hệ Mặt Trời. Nó giống như một vùng vật chất dày đặc nhưng không thể quan sát được. Khi tàu vũ trụ đi qua vùng vật chất này nó sẽ bị giảm tốc độ lại giống như một viên đạn đang bay từ môi trường không khí sang môi trường chất lỏng.

3. Vệt đỏ lớn trên sao Mộc

Trong lần đầu tiên quan sát sao Mộc, các nhà khoa học đã phát hiện ra một vệt đỏ lớn bí ẩn có hình tròn trên bầu khí quyển của hành tinh này. Kích thước của nó là đủ để 2 Trái đất chui vào, và nó vẫn tiếp tục di chuyển phía trên bầu khí quyển của sao Mộc. Cho đến nay các nhà khoa học vẫn chưa thể biết được nó là cái gì, nhưng chỉ biết rằng nó đã xuất hiện từ cách đây rất lâu rồi.

03

Đã có nhiều giả thuyết đặt ra nhằm giải thích xem nó là cái gì, cũng như sự hình thành và vì sao nó lại có kích thước lớn đến như thế cùng màu đỏ như gạch. Trong đó, nhiều nhà khoa học cho rằng đó là một cơn bão khổng lồ, nó được hình thành từ cách đây rất lâu và trên đường đi của mình nó hấp thụ các cơn bão nhỏ khác. Giống như một hố đen mini để tiếp tục tồn tại và lớn dần lên.

Màu đỏ của cơn bão là do các vật chất trên bề mặt của sao Mộc bị hút vào cơn bão. Tuy nhiên có giả thuyết khác cho rằng ánh sáng Mặt Trời phản chiếu cùng với một số hóa chất có trong khí quyển sao Mộc như amoniac và acetylene khiến cho nó có màu đỏ. Hiện tại thì vệt đỏ bí ẩn này vẫn tiếp tục di chuyển và lớn dần lên.

4. Thời tiết trên mặt trăng Titan của sao Thổ

Titan là Mặt trăng duy nhất của sao Thổ, điều thú vị là nó cũng có các mùa giống như Trái đất. Mỗi mùa trên Titan kéo dài khoảng 7 năm trên Trái đất, đó là vì sao thổ phải mất tới 29 năm để quay xong 1 vòng quanh Mặt Trời. Lần chuyển mùa gần đây nhất trên Titan là vào năm 2009, khi mà Bán cầu Bắc mùa đông chuyển sang mùa xuân và Bán cầu Nam vừa kết thúc mùa hè.

04

Tuy nhiên vào khoảng tháng 5 năm 2012, trong suốt mùa thu ở Nam bán Cầu của Titan, các nhà khoa học phát hiện hình ảnh của một cơn lốc xoáy cực lớn với đường kính lên đến 300km. Đây là điều không thể xảy ra trong điều kiện ấm áp tại Nam bán Cầu lúc đó.

Trong khi đó, bằng phương pháp phân tích quang phổ, các nhà khoa học phát hiện dấu hiệu của các hạt đông lạnh hydrogen cyanide (HCN) bên trong khi quyển của cơn bão này. Trong khi đó để hình thành được các các hạt đông lạnh HCN này thì nhiệt độ phải thấp hơn 100 độ C so với bình thường. Điều đó có nghĩa là thời tiết trên Titan đã có sự đảo chiều khó hiểu.

Hiện tại các nhà khoa học chưa thể lý giải được bí ẩn này. Cũng không có một giả thuyết nào được đặt ra, vì số lượng các hành tinh có sự phân bố mùa giống Trái đất là rất hiếm. Trong khi đó hiện tượng này cũng vô cùng đặc biệt. Nó khiến các nhà khoa học lo sợ điều tương tự có thể xảy ra đối với Trái đất.

5. Tia vũ trụ siêu năng lương cao

Tia vũ trụ với bức xạ năng lượng cao mà không giống như những tia bức xạ từ Mặt Trời là một trong những điều bí ẩn các nhà khoa học vẫn đang nghiên cứu. Cho đến nay chúng ta vẫn chưa biết được các tia vũ trụ này có nguồn gốc từ đâu, cũng như vì sao chúng lại có năng lượng lớn như vậy.

05

Trên Trái đất, các nhà khoa học cũng ghi nhận được những tia vũ trụ như vậy chiếu thẳng vào bầu khí quyển của chúng ta. Nhưng nhờ có tấm lá chắn vô hình mà các tia này không làm ảnh hưởng gì đến con người cũng như các thiết bị điện tử trên bề mặt Trái đất. Các tia vũ trụ này mang theo những hạt năng lượng cao nhất mà chúng ta từng thấy trong vũ trụ.

Có giả thuyết cho rằng các tia vũ trụ này đến từ những vụ nổ rất lớn do sự hình thành, sụp đổ hoặc va chạm của các ngôi sao lớn bên ngoài dải Ngân hà. Tuy nhiên theo quan sát thì sự xuất hiện của các tia vũ trụ này không trùng hợp với bất kỳ hiện tượng vũ trụ nào mà chúng ta có thể quan sát.

6. Hố đen có thể ngăn cản sự hình thành của các ngôi sao

Các nhà khoa học đã phát hiện ra rằng, trong những thiên hà lâu đời thì một hố đen khổng lồ có thể ngăn cản sự hình thành của những ngôi sao trẻ. Những ngôi sao trẻ trong quá trình bắt đầu hình thành sẽ hút những dòng bụi khí trong vũ trụ vào trung tâm của nó với vận tốc rất lớn. Sau đó sẽ có một quá trình nguội lạnh và dòng bụi khí này được ngưng tụ để tạo thành một ngôi sao mới.

06

Tuy nhiên theo như phát hiện mới đây, các nhà khoa học dự đoán rằng một hố đen khổng lồ có thể làm ảnh hưởng đến quá trình này. Họ thấy rằng trong một số thiên hà đỏ, chỉ có những ngôi sao già tồn tại mà không có ngôi sao trẻ nào mới được hình thành.

Theo như những kết quả nghiên cứu khác, các nhà khoa học cho rằng đây có thể là kết quả của một vụ va chạm giữa hai hố đen tại trung tâm của hai dải thiên hà khác nhau. Vụ va chạm đã tạo ra một vụ nổ lớn khiến cho các dòng khí bụi tồn tại trong thiên hà bị văng ra rất xa, khiến cho sau đó nó không còn nguyên liệu để hình thành những ngôi sao mới. Bên cạnh đó cũng có nhiều ý kiến cho rằng lực hấp dẫn rất lớn của hố đen khiến cho dòng bụi khí không thể tập trung để hình thành những ngôi sao (trong những thiên hà có kích thước nhỏ).

7. Khối lượng hố đen lớn hơn rất nhiều so với những gì chúng ta từng nghĩ

Các nhà khoa học tin rằng khối lượng của các hố đen khổng lồ tại trung tâm các thiên hà có khối lượng lớn hơn rất nhiều so với những tính toán trước đây, có thể lớn hơn gấp 40%. Thông qua việc quan sát và nghiên cứu hố đen được mệnh danh là “Eye of Sauron”, các nhà khoa học càng khẳng định được niềm tin trên.

07

Sở dĩ được gọi là Eye of Sauron là vì hố đen này có một đĩa bụi khổng lồ xung quanh khiến cho nó rất giống với hình dạng của con mắt Sauron trong bộ phim Lord of the Rings. Hố đen này nằm ở giữa thiên hà NGC 4151. Các nhà khoa học đã tính toán khoảng cách từ hố đen nay đến Trái đất là khoảng 13-95 triệu năm ánh sáng.

Tuy nhiên với một kỹ thuật hoàn toàn mới, các nhà khoa học đã có thể đo chính xác tới 95% khoảng cách từ Trái đất đến hố đen Eye of Sauron là khoảng 62 triệu năm ánh sáng. Nhờ kỹ thuật mới này đã giúp họ tính toán khối lượng của hố đen một cách chính xác hơn và theo như kết quả thu được thì con số đó lớn hơn gấp nhiều lần những gì chúng ta vẫn tin trước đó.

8. Bản chất lực hấp dẫn của hố đen

Cho đến nay, nhiều nhà khoa học vẫn tin rằng lực hấp dẫn không-thời gian là ổn định và không thể hỗn loạn. Tuy nhiên theo một nghiên cứu mới đây của 3 nhà khoa học Canada thì trường hấp dẫn có tính chất giống như chất lỏng. Nó là hỗn loạn, giống như một xoáy nước.

Lý thuyết này giúp giải thích sự hình thành những đĩa bụi hình xoáy ốc khi bị hút vào hố đen, tuy nhiên nó cũng mở ra một điều thắc mắc cũng như tò mò của nhiều người, đó là ở trung tâm của hố đen có lực hấp dẫn hay không. Nếu giống như một xoáy nước, nghĩa là bất vì vật chất nào tiếp xúc vào bên cạnh của hố đen sẽ bị nó nuốt chửng. Tuy nhiên nếu đi theo chiều vuông góc vào thẳng trung tâm của hố đen thì có thể sẽ không bị tác động của lực hấp dẫn.

Tuy nhiên đây mới chỉ là lý thuyết, các nhà khoa học vẫn còn phải tiến hành thêm nhiều nghiên cứu, có thể là thử nghiệm thực tế bằng phương pháp dò sóng hấp dẫn. Giống như việc đo gió trong những cơn bão, bằng cách gửi những tàu thám hiểm tiếp cận hố đen. Mặc dù vậy khoảng cách từ Trái đất đến các hố đen là rất xa, do đó sẽ còn rất lâu nữa chúng ta mới có thể khám phá hết những điều bí ẩn xung quanh hố đen.

9. Sự mất tích bí ẩn của các sao xung Pulsar

Sao Pulsar là những ngôi sao neutron tàn dư còn lại của những ngôi sao sao khi chết. Chúng có đặc điểm là tốc độ quay rất cao và phát ra bức xạ rất mạnh khiến chúng phát sáng như những ngọn hải đăng trong vũ trụ. Với số lượng ngôi sao đã chết rất nhiều tại trung tâm dải Ngân hà, các nhà khoa học dự tính phải có khoảng 50 Pulsar có thể quan sát được, tuy nhiên trên thực tế họ mới chỉ quan sát được một Pulsar trong dải Ngân hà.

09

Đó là một điều bí ẩn kỳ lạ, có khá nhiều giả thuyết khác nhau được đưa ra để giải thích điều này. Một trong số đó được công nhận là các sao Pulsar xoay với vận tốc rất cao này đã thu hút cả các hạt vật chất tối tại trung tâm của thiên hà. Trong khi vật chất tối là vô hình và không thể quan sát được, chúng ta chỉ có thể phát hiện qua tác động của lực hấp dẫn của vật chất tối lên những vật chất khác.

Chính vì vậy mà chúng ta không thể quan sát được các sao Pulsar này. Tuy nhiên không chỉ dừng lại ở đó, việc hút vật chất tối vào các ngôi sao Pulsar khiến cho khối lượng của chúng tăng lên gấp nhiều lần, đủ để hình thành nên một hố đen. Trong khi đó lượng vật chất tối tại trung tâm Ngân hà là rất dồi dào, có thể góp phần tạo nên nhiều hố đen mới.

10. Vũ trụ có thể được tạo ra từ một hố đen 4 chiều

Chúng ta vẫn đặt ra giả thuyết vũ trụ được hình thành từ một điểm kỳ dị với khối lượng vô cùng lớn bởi vụ nổ Big Bang, tuy nhiên các nhà khoa học chưa thể giải thích điểm kỳ dị này ở đâu ra. 3 nhà nghiên cứu tại Viện Perimeter đã đưa ra một giả thuyết mới để giải thích điều này.

10

Họ cho rằng vũ trụ của chúng ta bắt đầu từ một siêu tân tinh khổng lồ tự sụp đổ một phần tạo thành hố đen và sau đó nó hút phần vật chất còn lại để tạo thành một điểm kỳ dị có khối lượng vô cùng lớn. Tuy nhiên một hố đen thông thường là không thể hút toàn bộ vật chất ở xung quanh của nó vì chân trời sự kiện là một mặt phẳng 2D.

Do đó hố đen này phải có chân trời sự kiện 3D (3 chiều), đồng nghĩa với nó phải là một hố đen 4D. Trong khi đó vũ trụ hiện tại của chúng ta chỉ là vũ trụ 3 chiều, chúng ta vẫn chưa có kiến thức nào về chiều không gian thứ 4 này. Mặc dù vậy các nhà khoa học tỏ ra rất lạc quan với giả thuyết này, đơn giản vì nó vẫn còn bí ẩn mà có thể đến một lúc nào đó các nhà khoa học có thể lý giải.

Theo Listverse

[Dev Tip] Write a custom security token and handler in Windows Identity Foundation

In this article I will demonstrate how to write a token handler for a custom token in Windows Identity Foundation (WIF). The likely circumstances for requiring a new token type are:

  • The token type is pre-existing and needs to be federated
  • The new token type is an extension to a token type already supported by WIF

However, the purpose of this article is to demonstrate one of the extensibility points of WIF and so the reasons for creating a new token type are not so important.

Before continuing I should point out that I am not creating a new wire protocol for conveying tokens, but instead using an existing wire protocol (WS-Federation) to pass a new token type. WS-Federation is well suited to this because it is a means of conveying WS-Trust tokens using browser redirects. In turn, WS-Trust is essentially a container for …. well, any token type you like, and that is why we can create a new token type without risk of breaking any standards.

I am going to take a trial and error approach to this so that you can see some of the common pitfalls. I will simply fire a new token, wrapped within a WS-Trust 1.3 envelope and conveyed by the WS-Federation Passive Requestor Profile, as a sign-in response message to the Relying Party (RP) application. Then, I will fix the RP application by reacting to the errors that occur. Using this methodology you may get a fuller understanding of how to implement a new token handler and token.

First I need a Security Token Service (STS) to send a custom token to the RP. To do this I have captured a sign in response using Http Watch (a really good and easy to use HTTP sniffer), changed the token within the  WS-Federation wresult parameter, and hardcoded it as the STS response; this was achieved using a ASP.NET website with a HttpHandler implementation.

The custom token to be consumed by the RP looks like the following:

<m:MyCustomToken 
    xmlns:m="urn:mycustomtoken" 
    m:Id="D416881A-130B-4AFF-8091-F412D7440E39" 
    m:Issuer="urn:mycustomtokenhandlersts" 
    m:Audience="https://mycustomtokenhandlerwebsite/" 
    m:ValidFrom="2011-01-01" 
    m:ValidTo="2099-12-31">
    <m:Claim Name="GivenName" Namespace="urn:givenname">John</m:Claim>
    <m:Claim Name="Surname" Namespace="urn:surname">Doe</m:Claim>
    <m:Claim Name="Role" Namespace="urn:role">Manager</m:Claim>
    <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
        <SignedInfo>
            <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
            <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
            <Reference URI="">
                <Transforms>
                    <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
                    <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
                </Transforms>
                <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
                <DigestValue>gK8K+94DbXsVHxE8X3ulh45WcEM=</DigestValue>
            </Reference>
        </SignedInfo>
        <SignatureValue>… removed for brevity …</SignatureValue>
        <KeyInfo>
            <X509Data>
                <X509Certificate>… removed for brevity …</X509Certificate>
            </X509Data>
        </KeyInfo>
    </Signature>
</m:MyCustomToken>

It contains many of the characteristics of common token types:

  • Id – a unique identifier for the token (can be used to detect replay attacks)
  • Namespace – the namespace for the token
  • Issuer – the entity that issued the token to the RP
  • ValidFrom/ValidTo – the validity period of the token
  • Audience – the entity that the token is intended for
  • Claims – a set of authoritative statements about the identity described by the token
  • Digital signature – ensures that the message cannot be tampered with and also enforces the authoritative nature of the message

The <microsoft.identityModel> configuration section of the RP config file currently has no awareness of the new token type:

<microsoft.identityModel>
    <service>
        <audienceUris>
            <add value="https://mycustomtokenhandlerwebsite/" />
        </audienceUris>
        <federatedAuthentication>
            <wsFederation 
                passiveRedirectEnabled="true" 
                issuer="http://localhost:29460/MyCustomTokenHandlerSTS/STSHandler.ashx" 
                realm="https://mcthw" 
                requireHttps="false" />
            <cookieHandler requireSsl="false" />
        </federatedAuthentication>
        <issuerNameRegistry type="Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry">
            <trustedIssuers />
        </issuerNameRegistry>
    </service>
</microsoft.identityModel>

OK, by browsing to the RP home page the browser is automatically redirected to the STS and the custom token is returned to the RP … Error!

“ID4014: A SecurityTokenHandler is not registered to read security token (‘MyCustomToken’, ‘urn:mycustomtoken’)”

This seems reasonable as I have not yet made any effort to recognise the token. I therefore need the bare bones of a custom SecurityTokenHandler implementation:

using System;
using Microsoft.IdentityModel.Tokens;
 
/// <summary>
/// Summary description for MyCustomTokenHandler
/// </summary>
public class MyCustomTokenHandler : SecurityTokenHandler
{
    public MyCustomTokenHandler()
    {
    }
 
    public override string[] GetTokenTypeIdentifiers()
    {
        throw new NotImplementedException();
    }
 
    public override Type TokenType
    {
        get { throw new NotImplementedException(); }
    }
}
I also need a custom System.IdentityModel.Tokens.SecurityToken implementation representing the token type to be handled:
using System;
using System.IdentityModel.Tokens;
 
/// <summary>
/// Summary description for MyCustomToken
/// </summary>
public class MyCustomToken : SecurityToken
{
    public MyCustomToken()
    {
    }
 
    public override string Id
    {
        get { throw new NotImplementedException(); }
    }
 
    public override ReadOnlyCollection<SecurityKey> SecurityKeys
    {
        get { throw new NotImplementedException(); }
    }
 
    public override DateTime ValidFrom
    {
        get { throw new NotImplementedException(); }
    }
 
    public override DateTime ValidTo
    {
        get { throw new NotImplementedException(); }
    }
}

Finally, I need to reference the handler in the RP config file by adding in a <securityTokenHandlers> element:

<securityTokenHandlers>
    <add type="MyCustomTokenHandler" />
</securityTokenHandlers>

Browse to the RP again … Error!

“The method or operation is not implemented”
This occurs because I need to implement the handlers GetTokenTypeIdentifiers method to return the namespace of the new token, and also the TokenType property to return the tokens type:
public override string[] GetTokenTypeIdentifiers()
{
    return new string[] { "urn:mycustomtoken" };
}

public override Type TokenType
{
    get { return typeof(MyCustomToken); }
}

Browse to the RP again … Error!

“ID4014: A SecurityTokenHandler is not registered to read security token (‘MyCustomToken’, ‘urn:mycustomtoken’)”
 
This is a bit strange as I have implemented all of the methods mandated by the base class. After a quick peek into the WIF source I found the following:
 
public virtual bool CanReadToken(XmlReader reader)
{
    return false;
}
 
By default all security token handler implementations are excluded as possible candidates for parsing the token! I therefore need to override this method and perform some checks on the incoming token to make sure that my code genuinely can read the token:
public override bool CanReadToken(XmlReader reader)
{
    if (reader.LocalName.Equals("MyCustomToken") &&
        reader.NamespaceURI.Equals("urn:mycustomtoken"))
    {
        return true;
    }

    return false;
}

Browse to the RP again … Error!

“ID4008: ‘SecurityTokenHandler’ does not provide an implementation for ‘ReadToken’”
 
This seems self explanatory as I have indicated that I can read the token but have provided no code to do so. I need a SecurityToken implementation but the SecurityToken base class only offers a few read-only properties. It therefore seems that I must do most of the work myself. To achieve this I have created a internal representation of the token:
using System;
using System.Collections.Generic;
using Microsoft.IdentityModel.Claims;
 
/// <summary>
/// Summary description for MyCustomTokenInternal
/// </summary>
public class MyCustomTokenInternal
{
    public string Id { get; set; }
 
    public DateTime ValidFrom { get; set; }
 
    public DateTime ValidTo { get; set; }
 
    public string Audience { get; set; }
 
    public string Issuer { get; set; }
 
    public IEnumerable<Claim> Claims { get; set; }
}
I have then used the internal token type to populate an instance of MyCustomToken in the ReadToken implementation of MyCustomTokenHandler:
public override SecurityToken ReadToken(XmlReader reader)
{
    // Check token signature using EnvelopedSignatureReader (more performant but more complex to use)
    // or SignedXml (easier to use but less performant)

    MyCustomToken token = new MyCustomToken(
        new MyCustomTokenInternal()
        {
            Id = reader.GetAttribute("Id", TokenNamespace),
            ValidFrom = XmlConvert.ToDateTime(reader.GetAttribute("ValidFrom", TokenNamespace)),
            ValidTo = XmlConvert.ToDateTime(reader.GetAttribute("ValidTo", TokenNamespace)),
            Audience = reader.GetAttribute("Audience", TokenNamespace),
            Issuer = reader.GetAttribute("Issuer", TokenNamespace),
            Claims = from el in XElement.Load(reader).Elements(XName.Get("Claim", TokenNamespace)) select new Claim(el.Attribute("Namespace").Value, el.Value)
        });

    return token;
}

Browse to the RP again … Error!

“ID4011: A SecurityTokenHandler is not registered to validate token type ‘MyCustomToken’”
Ok … as well as being able to read the token, the token handler also needs to be able to validate it. I therefore need to override the ValidateToken method and I am also going to pre-empt the possibility that I need to provide a ValidateToken implementation (as for CanReadToken/ReadToken):
public override bool CanValidateToken
{
    get
    {
        return true;
    }
}
 
public override ClaimsIdentityCollection ValidateToken(SecurityToken token)
{
    ClaimsIdentityCollection idColl = new ClaimsIdentityCollection();
    IClaimsIdentity id = new ClaimsIdentity((token as MyCustomToken).Claims);
    idColl.Add(id);
    return idColl;
}
Browse to the RP again … Success!
image_2_2F5D7C2C[1]
Finally, to prove that the token conditions are being honoured by WIF I have changed the ValidTo date to a time in the past. When I now attempt to browse to the RP I get the following error:
“Specified argument was out of the range of valid values. Parameter name: validFrom”
Which is slightly odd as it is the ValidTo date that is incorrect, but at least an error occurs.
In conclusion, I have shown how it is possible to consume a custom token type in WIF using the SecurityToken and SecurityTokenHandler classes.
Finally I have included the complete classes below for reference.
Have fun!
Written by Bradley Cotier
MyCustomTokenHandler:
using System;
using System.IdentityModel.Tokens;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using Microsoft.IdentityModel.Claims;
using Microsoft.IdentityModel.Tokens;

/// <summary>
/// Summary description for MyCustomTokenHandler
/// </summary>
public class MyCustomTokenHandler : SecurityTokenHandler
{
    private const string TokenNamespace = "urn:mycustomtoken";

    public MyCustomTokenHandler()
    {
    }

    public override string[] GetTokenTypeIdentifiers()
    {
        return new string[] { TokenNamespace };
    }

    public override Type TokenType
    {
        get { return typeof(MyCustomToken); }
    }

    public override bool CanReadKeyIdentifierClause(XmlReader reader)
    {
        if (reader.LocalName.Equals("X509Data"))
        {
            return true;
        }

        return false;
    }

    public override bool CanReadToken(XmlReader reader)
    {
        if (reader.LocalName.Equals("MyCustomToken") &&
            reader.NamespaceURI.Equals("urn:mycustomtoken"))
        {
            return true;
        }

        return false;
    }

    public override SecurityToken ReadToken(XmlReader reader)
    {
        // Check token signature using EnvelopedSignatureReader (more performant but more complex to use)
        // or SignedXml (easier to use but less performant)

        MyCustomToken token = new MyCustomToken(
            new MyCustomTokenInternal()
            {
                Id = reader.GetAttribute("Id", TokenNamespace),
                ValidFrom = XmlConvert.ToDateTime(reader.GetAttribute("ValidFrom", TokenNamespace)),
                ValidTo = XmlConvert.ToDateTime(reader.GetAttribute("ValidTo", TokenNamespace)),
                Audience = reader.GetAttribute("Audience", TokenNamespace),
                Issuer = reader.GetAttribute("Issuer", TokenNamespace),
                Claims = from el in XElement.Load(reader).Elements(XName.Get("Claim", TokenNamespace)) select new Claim(el.Attribute("Namespace").Value, el.Value)
            });

        return token;
    }

    public override bool CanValidateToken
    {
        get
        {
            return true;
        }
    }

    public override ClaimsIdentityCollection ValidateToken(SecurityToken token)
    {
        ClaimsIdentityCollection idColl = new ClaimsIdentityCollection();

        IClaimsIdentity id = new ClaimsIdentity((token as MyCustomToken).Claims);

        idColl.Add(id);

        return idColl;
    }
}
MyCustomToken:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IdentityModel.Tokens;
using Microsoft.IdentityModel.Claims;

/// <summary>
/// Summary description for MyCustomToken
/// </summary>
public class MyCustomToken : SecurityToken
{
    private MyCustomTokenInternal tokenInt;

    public MyCustomToken(MyCustomTokenInternal tokenInt)
    {
        this.tokenInt = tokenInt;
    }

    public override string Id
    {
        get { return this.tokenInt.Id; }
    }

    public override ReadOnlyCollection<SecurityKey> SecurityKeys
    {
        get { return null; }
    }

    public override DateTime ValidFrom
    {
        get { return this.tokenInt.ValidFrom; }
    }

    public override DateTime ValidTo
    {
        get { return this.tokenInt.ValidTo; }
    }

    public IEnumerable<Claim> Claims
    {
        get { return this.tokenInt.Claims; }
    }
}
MyCustomTokenInternal:
using System;
using System.Collections.Generic;
using Microsoft.IdentityModel.Claims;
 
/// <summary>
/// Summary description for MyCustomTokenInternal
/// </summary>
public class MyCustomTokenInternal
{
    public string Id { get; set; }
 
    public DateTime ValidFrom { get; set; }
 
    public DateTime ValidTo { get; set; }
 
    public string Audience { get; set; }
 
    public string Issuer { get; set; }
 
    public IEnumerable<Claim> Claims { get; set; }
}
RP web.config <microsoft.identityModel> config section:
<microsoft.identityModel>
    <service>
        <audienceUris>
            <add value="https://mycustomtokenhandlerwebsite/" />
        </audienceUris>
        <federatedAuthentication>
            <wsFederation 
                passiveRedirectEnabled="true" 
                issuer="http://localhost:29460/MyCustomTokenHandlerSTS/STSHandler.ashx" 
                realm="http://localhost:58724/MyCustomTokenHandlerWebsite/" 
                requireHttps="false" />
            <cookieHandler requireSsl="false" />
        </federatedAuthentication>
        <issuerNameRegistry type="Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry">
            <trustedIssuers />
        </issuerNameRegistry>
        <securityTokenHandlers>
            <add type="MyCustomTokenHandler" />
        </securityTokenHandlers>
    </service>
</microsoft.identityModel>

REF: 
http://blogs.msdn.com/b/mcsuksoldev/archive/2011/03/25/write-a-custom-security-token-and-handler-in-windows-identity-foundation.aspx
https://github.com/IdentityServer/Thinktecture.IdentityServer3

[Dev Tip] Single Sign On (SSO) for cross-domain ASP.NET applications

Introduction

“Yet another Monday morning at your office. Just started to wonder how fast the weekend just passed away, and how hard are the days going to be in this week. You got a mail! Oh no, not a lucrative job offer. It’s just another requirement from your new client. Your client has a number of ASP.NET of sites, and all he wants is to enable his users to log onto all sites just by logging into a single site. And, obviously, log out of all sites by logging out from a single site.

OK, so your client wants a “Single Sign On” implementation. You thought, oh, it’s not that hard. The ASP.NET forms authentication is the solution. It allows to share the same cookie across sites under the same domain using the same configuration key using the <machineKey> element. A quick Google search gives you some pretty fine examples of Single Sign On implementation using <machineKey> in ASP.NET applications. OK, so life is not that hard as a programmer after all.

Hold on. Something just caught your eyes. The requirement says, “your client has a number of sites, but they are not necessarily under the same domain“. You just missed this important point, and, the very first day at your office just started to appear harder to you. It’s not easy to implement a Single Sign On for sites under different domains, for the very fundamental reason that, the cookie of a particular domain cannot be shared with another domain. Who doesn’t know that it’s the cookie that is used to maintain authentication information across different page requests?”

I just depicted a scenario that is pretty common these days. This is an era of web 2.0 and social networking, as they say. Standalone and “island” like systems are very rare these days. You do a tweet from Twitter and update your status at LinkedIn and Facebook at the same time without doing anything else. You write an article on CodeProject and share it on hundreds of sites within seconds. So, it’s pretty natural that you would expect to log onto a site and jump to another related one without having to re-login again, doesn’t matter to you what domain these sites are deployed under.

So I thought, how about developing something that allows to easily implement a Single Sign On (of course, for ASP.NET sites) for cross-domain sites? People may have tried to implement this in many different ways, and commercial solutions are also available upon purchase. But, what if I try to develop something which is simple, free, and most importantly, works.

How authentication works in ASP.NET

Well, this may not be something new to you. But while we try to solve the hardest problems on earth, we often need to go back to the basics, to try to understand how things really work. So, it wouldn’t be bad to revisit the ABS’s of the ASP.NET forms authentication mechanism.

Here is how ASP.NET forms authentication works:

Forms_Authentication.png

Forms authentication in ASP.NET

Sequence of operations:

  • You hit a page in an ASP.NET application which is accessible only to the authenticated users (logged in users) of the application.
  • The ASP.NET runtime looks for a cookie (forms authentication cookie) in the request, and if the cookie is not found, it redirects the current request to the login page (the login page location is configured in theweb.config).
  • You provide the login credentials and hit the “Login” button. Assuming that the credentials are correct, your system validates the credentials successfully against the data storage, sets the user name to theThread.CurrentPrincipal.Identity.Name property, writes a cookie in the Response (along with setting the cookie value with the user information and setting some properties like cookie name, expiration date/time etc.), and redirects to the originally requested page.
  • You hit another page in the application (or, click a link that navigates you to another page), and your browser sends the authentication cookie (with any other cookie that were written by the same site earlier), this time because the browser has already got the authentication cookie in the last response.
  • As usual, the ASP.NET runtime looks for the authentication cookie in the request, and this time it finds the cookie. It checks the cookie properties (like expiration date/time, path etc.), and if not expired already, reads the cookie value, retrieves the user information from the cookie, sets the user name into theThread.CurrentPrincipal.Identity.Name property, checks if the user has permission to access the requested page, and if he/she has permission, executes and serves the page to the browser.

Pretty simple, right?

How authentication works in multiple ASP.NET sites under the same domain

As depicted already, the ASP.NET forms authentication is fully dependent on Cookies. So, if two different sites can share the same authentication cookie, it is possible to make the same user to log onto both sites just by logging onto a single site.

Now, the HTTP protocol says, two different sites can share a cookie if and only if both sites are deployed under the same domain (or, sub-domain). Internally, your browser stores the cookies locally (either in disk or in memory) against the web site’s URL. When you hit subsequent requests to any site, the browser reads those cookies which have matching domain or sub domain names comparing to the currently requested URL and sends those cookies with the request.

So, let’s assume you have two sites as follows:

These two sites share the same host address (same domain mydomain.dom and sub-domain www), and both sites have been configured to use forms authentication for user authentication and authorization. Suppose you just logged onto the http://www.mydomain.com/site1 site, and as described above, your browser now has a forms authentication cookie originated from http://www.mydomain.com/site1.

Now, if you click/hit any subsequent URL of http://www.mydomain.com/site1, the forms authentication cookie will be sent with the request. Why? Because the cookie originally belongs to this site? Yes, but not 100% correct. To be exact, the cookie will be sent because the request URL http://www.mydomain.com/site1 has the same matching sub domain and domain name (host address) http://www.mydomain.com.

So, after you logged onto http://www.mydomain.com/site1, if you hit a URL of http://www.mydomain.com/site2, the forms authentication cookie (or, any other matching cookie) will be sent with the request for the very same reason thathttp://www.mydomain.com/site2 has the same matching host address (http://www.mydomain.com) even if this is a different application (site2). So, it’s obvious that as the same forms authentication cookie can be shared across two different web applications under the same host address, it’s possible to log onto both web applications at the same time just by logging onto a single web application (hence, Single Sign On).

However, ASP.NET does not allow you to automatically implement Single Sign On just by implementing forms authentication for multiple web applications under the same host address. Why? Because, each different ASP.NET web application uses its own encryption keys to encrypt and decrypt cookie data (and other data such as ViewState) for ensuring security. So, unless you specify a single encryption key for each ASP.NET application, cookies will be sent from the browser, but an application won’t be able to read the value of an authentication cookie that is originated from another application.

Specifying a single authentication key is the solution. For each ASP.NET application, you have to use the same<machinekey> element in the web.config, as follows:

<machineKey 
  validationKey="21F090935F6E49C2C797F69BBAAD8402ABD2EE0B667A8B44EA7DD4374267A75D" 
  decryptionKey="ABAA84D7EC4BB56D75D217CECFFB9628809BDB8BF91CFCD64568A145BE59719F"
  validation="SHA1"
  decryption="AES"/>

If the same machinekey (with validationKey and decryptionKey) is used across all applications under the same domain, each will be able to read cookie values that originate from other applications.

What if two sites have the same domain and different sub domains?

Suppose your sites are deployed under the following host addresses:

  • site1.mydomain.com
  • site2.mydomain.com

These two sites share the same domain (same second level domain mydomain.com), but has differ in their third level domain (different sub domains site1 and site2).

By default, the browser sends cookies for matching host addresses only (matching domain and sub domain). So, the site site1.mydomain.com won’t get any cookie that is originated form site2.mydomain.com (because they don’t have the same host address, their sub domain is different), and hence even if you configure the samemachineKey for these two sites, one site won’t be able to get cookies originated from another.

So, in addition to implementing the same machineKey across all sites, you also need to instruct the ASP.NET runtime to define the domain of an authentication cookie so that the cookie is sent from the browser with any request that has a matching domain name. You need to configure the forms authentication cookie as follows:

<forms name="name" loginUrl="URL" defaultUrl="URL" domain="mydomain.com"/>

So, how to share the authentication cookie across multiple domains?

Well, there is absolutely no way to do that. The fundamental barrier of the HTTP protocol prevents you from sharing a cookie across different domains, primarily for security reasons.

Say, you have the following two sites under two different domains:

Now, if you log onto the http://www.domain1.com site using forms authentication, and hit a URL of http://www.domain2.com, the browser wouldn’t simply send the authentication cookie of domain1.com to domain2.com. There is no built-in mechanism in ASP.NET to implement a Single Sign On across these two different sites.

In order to implement a Single Sign On across these two sites under different domains, there has to be some “workaround” or some implementation model that lets these two sites to access a single cookie via some “indirect” mechanism.

A very basic Cross Domain SSO implementation model

Suppose we have to implement SSO in these sites:

In order to implement SSO across these sites, we need to set an authentication cookie to the client’s browser for all three sites when the user is authenticated in any one of the two sites.

If user1 is authenticated into http://www.domain1.com, the browser will add an authentication cookie in the response before returning a response to http://www.domain1.com. But as we need to be able to log onto http://www.domain2.com andhttp://www.domain3.com, we need to set the authentication cookie in the same client’s browser for http://www.domain2.comand http://www.domain3.com at the same time. So, before returning the response to the browser, http://www.domain1.comwill have to redirect the browser to http://www.domain2.com and http://www.domain3.com for setting the authentication cookie.

The following diagram depicts the basic idea (arrows denote the redirections):

Basic_SSO_model_overview.png

Basic SSO model overview

And the following sequence diagram depicts the detailed idea:

SSO_Basic_Model.png

Sequence diagram of login operation in the basic SSO model

Sequence of operations:

[Status:  browser has no authentication cookie]
[Status:  browser has no authentication cookie]
[Status:  browser has no authentication cookie]
    • User provides login credentials and hits the “Login” button. Browser sends a POST request tohttp://www.domain1.com.
[Status:  browser has no authentication cookie]
    • http://www.domain1.com accepts the login credentials, verifies the credentials against a data storage, and upon success, marks the user as logged in, creates an authentication cookie with the logged in user’s information, and adds to the response.
[Status : browser has no authentication cookie]
[Status : browser has no authentication cookie]
[Status : browser has an authentication cookie for www.domain2.com]
    • http://www.domain2.com immediately redirects to the ReturnUrl address with setting an authentication cookie for http://www.domain1.com by reading the cookie value from the request. Eventually, the authentication cookie is also sent with the response with the redirect command.
[Status : browser has an authentication cookie for www.domain2.com]
[Status : browser has authentication cookie 
          for both www.domain1.com and www.domain2.com].
    • http://www.domain1.com sees that the authentication cookie is already set in the request, and hence instead of returning the login page, it serves the page that is requested.
[Status : browser has authentication cookie 
          for both www.domain1.com and www.domain2.com].

So, if the user hits an authenticated page of http://www.domain2.com in the browser now, as the authentication cookie is already stored in the browser for http://www.domain2.com, it is sent with the request, and http://www.domain2.comretrieves the user information from the authentication cookie, logs in the user, and serves the page output that is requested by the user.

As the browser now has authentication cookie for both http://www.domain2.com and http://www.domain3.com, the user is considered to be logged onto both sites, and hence, Single Sign On implemented in both sites Smile | :)

How about “Single Sign Out”?

If we only had to manage logging onto the sites for users, we would have been done so far. But as part of the Single Sign On, we also have to manage the “Single Sign Out“. That is, when a user signs out of a single site, the user should be marked as signed out of all the sites.

Internally, when the user signs out of a single site (say, http://www.domain1.com), the authentication cookie of all the other sites needs to be removed from the browser (in this case, http://www.domain2.com). A cookie can be removed from the browser by removing the cookie from the response before sending the response to the browser.

To remove all authentication cookies for all sites, the same flow of request-redirect-response described above should be followed, and instead of setting the authentication cookie, this time, the authentication cookie should be removed from the response.

Problem with this SSO model

This model should work fine for two sites. For logging into a site and for logging out of a site, the corresponding site internally should follow a request-redirect-response flow with all other sites (that are under the SSO). Once the user is logged onto a single site, for all other subsequent requests onto any site, there is no request-redirect-response loop because each site has its own authentication cookie stored in the browser.

But, if the number of sites is more than 2, the complexity increases. That is, if the user logs ontohttp://www.domain1.com, this site will internally redirect to http://www.domain2.com and http://www.domain3.com for setting the authentication cookie in the browser, and finally http://www.domain3.com redirects to http://www.domain1.com and serves the originally requested page output to the browser. This will make the login and logout functionality of each site costly and complex. What if there are more than 3 sites? What if we have 20+ sites to integrate under a Single Sign On model which are deployed under different domains? The model is obviously not going to fit.

This model also requires each site to have knowledge about all the other sites which are configured under the same SSO umbrella (because, each site has to redirect to all the other sites for setting the authentication cookie). Also, this would require the user authentication and authorization logic to be implemented on each site.

It’s clearly understandable, as the number of sites increases, this SSO model will become messy, and will lose its acceptance, and this model can’t really be considered a “generic” SSO model for cross-domain applications. So, we need a better model that should be independent of the number of sites to integrate under a Single Sign On umbrella.

A proposed model for Cross-Domain SSO

The previous model was kind of a “mesh-up” where each site had to be redirected to N-1 other sites for setting and removing authentication cookie for signing in and signing out. Each site had to be configured with knowledge of the other N-1 sites, and some complex and expensive login and logout logic had to be implemented.

What if we maintained a single authentication cookie for tracking user authentication for all sites? What about introducing a new dedicated site for authenticating users and setting the authentication cookie? Sounds lucrative.

In order to implement SSO for multiple sites, the user database should be a unified one, and hence, it makes good sense to implement the user authentication and authorization functionality within a dedicated site where each site can access the functionality via a web or WCF service. This allows the sites to get rid of redundant user authentication/authorization functionality. But the most important question is how this dedicated site is going to help implement Single Sign On.

Well, in this model, the browser will not store authentication cookies for each site. Rather, it will store an authentication cookie for only the dedicated site which will be used by the other sites to implement Single Sign On. Let’s call this site http://www.sso.com.

In this model, each and every request to any site (which takes part in the SSO model) will internally be redirected to the SSO site (http://www.sso.com) for setting and checking the existence of an authentication cookie. If the cookie is found, the authenticated pages are served to the browser, and if not, the user is redirected to the login page of the corresponding site.

The following diagram depicts the basic idea:

Proposed_SSO_model_overview.png

A dedicated site (www.sso.com) for managing the authentication cookie in the proposed SSO model

For understanding the model in detail, let’s imagine we are trying to apply this model to implement SSO to the following two sites:

We now have a dedicated site for managing the authentication cookie: http://www.sso.com.

Here is how this model works:

SSO_Proposed_Model_Part_I.png

Sequence diagram of an authenticated page hit without login

Sequence of operations:

SSO_Proposed_Model_Part_II.png

Sequence diagram of login
    • User provides credentials and hits the “Login” button. No postback request is redirected to the SSO site in this model. This time, http://www.domain1.com invokes the web/WCF service of http://www.sso.com to check the provided user credentials, and upon success, returns the user object with a Token property that is generated each time a user logs in (say, a GUID).
    • http://www.domain1.com marks the user as logged in (say, stores the user object in the session), prepares a URL with the user Token, and redirects the current request to http://www.sso.com to set the authentication cookie, with the ReturnUrl parameter value being set to the original request URL.
    • http://www.sso.com checks the incoming request URL, and finds a user Token, with no authentication cookie being available yet. That indicates the user has been authenticated into the client site (http://www.domain1.com), and the authentication cookie now needs to be set in the browser for http://www.sso.com. So, it retrieves the user from the cache using the Token, prepares an authentication cookie with the user info and sets the other cookie properties (expiration date/time etc.), adds the cookie to the response, and finally redirects to the originally requested URL found in the ReturnUrl query string value (a URL of http://www.domain1.com), along with adding the Token value in the query string.
    • Browser gets a Redirect command now with ReturnUrl to http://www.domain1.com, and also gets an authentication cookie from http://www.sso.com. So it stores the authentication cookie for http://www.sso.com and hits a request to the URL at http://www.domain1.com.
    • http://www.domain1.com now sees that a user Token is present in the query string. It validates the user token via a web/WCF service call at http://www.sso.com, and upon validation, executes the originally requested page ofhttp://www.domain1.com and writes the output in the response.

SSO_Proposed_Model_Part_III.png

Sequence diagram of browsing an authenticated page after login
  • User hits an authenticated page URL of http://www.domain2.com now.
  • http://www.domain2.com redirects the request to http://www.sso.com in the client’s browser, setting the ReturnUrlproperty to the originally requested URL of http://www.domain2.com.
  • The browser gets a redirect command to http://www.sso.com. It already has an authentication cookie stored for this site, and hence adds this cookie to the request before sending the request to http://www.sso.com.
  • http://www.sso.com checks the incoming request and looks for any authentication cookie in the request. If finds an authentication cookie this time, and checks whether the cookie has expired. If not, it retrieves the userToken from the cookie and redirects the request to the originally requested page URL athttp://www.domain2.com, setting the user Token in the query string.
  • http://www.domain2.com sees that there is a user Token presented in the query string. So it validates the user token via a web/WCF service call at http://www.sso.com, and upon validation, executes the originally requested page of http://www.domain2.com and writes the output in the response.

To summarize

Wow, sounds a lot of things are happening! Let me summarize those here:

Initially, the browser doesn’t have any authentication cookie for http://www.sso.com. So, hitting any authenticated page in the browser for http://www.domain1.com or http://www.domain2.com redirects the user to the login page (via an internal redirection to http://www.sso.com for checking the existence of the authentication cookie). Once a user is logged onto a site, the authentication cookie for http://www.sso.com is set in the browser with the logged in user information (most importantly, the user Token, which is valid only for the user’s login session).

Now, if the user hits any authenticated page URL of http://www.domain1.com or http://www.domain2.com, the request is internally redirected to http://www.sso.com in the user’s browser, and the browser sends the authentication cookie, which is already set. http://www.sso.com finds the authentication cookie, extracts the user Token, and redirects to the originally requested URL in the browser with the user token, and the originally requested site validates the Tokenand serves the page that was originally requested by the user.

Once the user is logged onto any site under this SSO model, hitting any authenticated page onhttp://www.domain1.com or http://www.domain2.com results in an internal redirection to http://www.sso.com (for checking the authentication cookie and retrieving the user Token) and then serving the authentication page in the browser output.

Traffic

Following are the summary of traffic for each different scenario:

Scenario1: A public page hit

A trip from the browser to the client site + a trip from the client site to the browser

Summary: 1 Request + 1 Response

Scenario2: An authenticated page hit without login

A trip from the browser to the client site (page hit) + a redirect command to the browser and a trip from the browser to the SSO site (redirect to SSO for cookie check) + a redirect command to the browser and a trip from the browser to the client site (redirect to client site without cookie) + a trip from the client site to the browser (login page)

Summary: 1 Request + 2 Redirects + 1 Response

Scenario3: Login

A trip from the browser to the client site (postback) + an invocation of the authentication Web Service (user authentication) + a redirect command to the browser and and a trip from the browser to the SSO site (redirect to SSO with Token) + a redirect command to the browser and a trip from the browser to the client site (setting the authentication cookie) + an invocation of a Web Service method (Token validation) + a trip from the client site to the browser (serve the authenticated page)

Summary: 1 Request + 2 Web Service calls at the server + 2 Redirects + 1 Response

Scenario4: Browse an authenticated page while logged in

A trip from the browser to the client site (URL hit) + a redirect command to the browser and a trip from the browser to the SSO site (redirect to SSO with the authentication cookie) + a redirect command to the browser and a trip from the browser to the client site (checking the authentication cookie) + an invocation of a Web Service method (Token validation) + a trip from the client site to the browser (serve the page)

Summary: 1 Request + 2 Redirect + 1 Web Service call at the server + 1 Response

Scenario5: Logout

A trip from the browser to the client site (click logout link) + a redirect command to the browser and a trip from the browser to the SSO site (instruction to log out) + a redirect command to the browser and a trip from the browser to the client site (after removing the authentication cookie) + a trip from the client site to the browser (serve the login page)

Summary: 1 Request + 2 Redirects + 1 Response

Trade offs

Comparing the basic SSO model and the proposed one, it seems the basic model suits well for 2, or, at most 3 sites. In the case of the basic SSO model, the login and logout functionality will be complex in terms of implementation and execution time, but the subsequent pages would be served in a normal request-response cycle (1 request and 1 response).

However, extending the SSO is difficult (addition of new sites), configuration management is maximum (each site has to manage their own cookie and each has dependency on other sites), and redundant user authentication functionality has to be implemented on multiple sites.

On the other hand, the proposed model does not have any dependency on the number of sites to integrate (you can add as many sites as you want and the SSO model won’t change). The configuration management is minimum for the participating site (no site has to know about the other sites, they need only know about the SSO site), the authentication and cookie management is handed over to a dedicated site (http://www.sso.com), the SSO related functionality can be easily integrated with the site if implemented smartly, and could be extended easily (new sites could be added easily).

However, there is a small performance penalty. Unlike the basic model, each authenticated page access requires3 requests from the browser to the SSO site and the client site (the two additional requests are due to the two internal redirects). Though, the additional two requests take minimum execution time (a blank redirect request, just for setting and checking the cookie), and hence could be considered acceptable considering today’s high Internet bandwidth availablilty.

Implementation of the proposed SSO model

Enough theories, and we should be prepared well enough now to get our hands dirty with some code. Good that I already have done it and have shared it in the next article. The following article would present an actual implementation of the proposed SSO model for Cross-Domain ASP.NET sites, and would point out some implementation challenges and their workarounds:

The implementation

Yes, I have done a sample SSO application based on the proposed model. It’s not just another “Hello world”, it’s a working application that implements SSO across three different sites under three different domains. The hard work is done, and the soft output is, you just need to extend a class for making an ASPX page a “Single Sign On” enabled one in your ASP.NET application. You, of course, have to set up an SSO site and configure your client applications to use the SSO site, that’s all (merely a 10 minute work).

The SSO implementation is based on the following high level architecture:

CrossDomainSSOExample/Proposed_SSO_model_overview.png

Figure : The Single Sign On implementation model

There may be unlimited number of client sites (in our example, 3 sites) which could participate under a “Single Sign On” umbrella, with the help of a single “Single Sign On” server (call this the SSO site, http://www.sso.com). As described in the previous article, the browser will not store an authentication cookie for each different client site in this model. Rather, it will store an authentication cookie for only the SSO site (http://www.sso.com) which will be used by the other sites to implement Single Sign On.

In this model, each and every request to any client site (which takes part in the SSO model) will internally be redirected to the SSO site (http://www.sso.com) for setting and checking the existence of the authentication cookie. If the cookie is found, the authenticated pages for the client sites (that are currently requested by the browser) are served to the browser, and if not found, the user is redirected to the login page of the corresponding site.

How it works

Initially, the browser doesn’t have any authentication cookie stored for http://www.sso.com. So, hitting any authenticated page in the browser for http://www.domain1.com, or http://www.domain2.com, or http://www.domain3.com redirects the user to the login page (via an internal redirection to http://www.sso.com for checking the existence of the authentication cookie). Once a user is logged onto a site, the authentication cookie for http://www.sso.com is set in the browser with the logged in user information (most importantly, the user Token, which is valid only for the user’s login session).

Now, if the user hits any authenticated page URL of http://www.domain1.com, or http://www.domain2.com orhttp://www.domain3.com, the request is internally redirected to http://www.sso.com in the user’s browser and the browser sends the authentication cookie, which is already set. http://www.sso.com finds the authentication cookie, extracts the user Token, and redirects to the originally requested URL in the browser with the user token, and the originally requested site validates the Token and serves the page that was originally requested by the user.

Once the user is logged onto any site under this SSO model, hitting any authenticated page onhttp://www.domain1.com, or http://www.domain2.com, or http://www.domain3.com results in an internal redirection to http://www.sso.com(for checking the authentication cookie and retrieving the user Token) and then serving the authentication page in the browser output.

OK, show me what you implemented

I have developed a sample Single Sign On application that incorporates three different sites (http://www.domain1.com,http://www.domain2.com, and http://www.domain3.com) and an SSO server site (http://www.sso.com). The sample SSO implementation code is available for download with this article. You just need to download and set up the sites as instructed in the next section. Once you are done with that, you can test the implementation in different scenarios.

The following section has step by step instructions to test the Single Sign On functionality in different scenarios, and each testing scenario has a Firebug network traffic information that depicts the total number of requests (including the lightweight redirect requests) and their length in size. The number of redirect requests and their length in size are marked within green for easy understandability.

Scenario1: Before authentication

Hit the following three URLs in the browser in three different tabs of the same browser window.

CrossDomainSSOExample/LoginUrlSSO.png

Three different login screens will be presented in each different tabs, for each different site:

CrossDomainSSOExample/Login1.png

CrossDomainSSOExample/Login2.png

CrossDomainSSOExample/Login3.png

Traffic info

For presenting the login screen, in total, four requests are sent to the servers, among which three are redirect requests (marked in green). The redirect request sizes are very small (in terms of bytes), and are negligible even considering network latency.

CrossDomainSSOExample/TrafficLogin.png

Scenario 2: Authentication

Use one of any following credentials in any one of the login screens to log on. Let’s log onto http://www.domain1.comwith user1/123.

Available credentials:

  • user1/123
  • user2/123
  • user3/123

After login, the following screen will be provided for user1 onto http://www.domain1.com.

CrossDomainSSOExample/Home1.png

Traffic info

For login, in total, three requests are sent to the servers, among which two are redirect requests (marked in green). The redirect request sizes are very small (in terms of bytes), and are negligible even considering network latency.

CrossDomainSSOExample/TrafficAuthentication.png

Scenario 3: After authentication

As user1 has logged on to http://www.domain1.com, he should be logged onto the other remaining sites:http://www.domain2.com and http://www.domain3.com at the same time, if those sites are browsed in the same window or in different tabs in the same window. Hitting an authenticated page in http://www.domain2.com and http://www.domain3.comshould not present a login screen.

Let’s just refresh the current page at http://www.domain2.com and http://www.domain3.com in their corresponding window (currently, the login screen is being shown in the browser):

CrossDomainSSOExample/Home2.png

CrossDomainSSOExample/Home3.png

You will see that instead of showing the login screen, the authenticated home page is being shown. So, user1 is logged onto all three sites: http://www.domain2.com, http://www.domain2.com, and http://www.domain3.com.

Each home page shows a “Go to Profile Page” link which you can click to navigate to another page. This demonstrates that clicking on hyperlinks and navigating to other pages in the application also works without any problem.

Traffic info

For browsing authenticated pages after login, in total, 3 requests are sent to the servers, among which 2 are redirect requests (marked in green). The redirect request sizes are also very small (in terms of bytes), and are negligible even considering network latency.

CrossDomainSSOExample/TrafficAuthentication.png

Scenario 4: Hit the authenticated page on a different session

As expected, the user’s “Sign on” status should only be valid for the current session ID, and any authenticated page URL hit to any one of the three sites will be successful if the URL is hit in the same browser window or in a different tab of the same browser window. But, if a new browser window is opened, and an authenticated URL is hit there, it should not be successful and the request should be redirected to the login page (because that is a different browser session).

To test this, open a new browser window and hit any URL of the three sites that points to an authenticated page (you can copy and paste the existing URL addresses). This time, instead of showing the page output, you will see the request will be redirected to the login page as follows (assuming that you hit a URL of http://www.domain3.com):

CrossDomainSSOExample/Login3.png

Traffic info

For hitting an authenticated page on a different session, in total, 4 requests are sent to the servers, among which 3 are redirect requests (marked in green). The redirect request sizes are very small (in terms of bytes), and are negligible even considering network latency.

CrossDomainSSOExample/TrafficLogin.png

Log out

To log out of the sites, click on the “Log out” link of the home page of http://www.domain1.com. The system will log outuser1 from the site http://www.domain1.com and will redirect to the login screen again:

CrossDomainSSOExample/Login1.png

Traffic info

For logging out, in total, 4 requests are sent to the servers, among which 3 are redirect requests (marked in green). The redirect request sizes are very small (in terms of bytes), and are negligible even considering network latency.

CrossDomainSSOExample/TrafficLogout.png

After log out

As user1 is logged out of the site http://www.domain1.com, he should be logged out from http://www.domain2.com andhttp://www.domain3.com at the same time. So, hitting any authenticated page URL of http://www.domain2.com andhttp://www.domain3.com should now redirect to their corresponding login screens.

To test this, refresh the current page of http://www.domain2.com and http://www.domain3.com. Instead of refreshing the page, the system will now redirect the requests to their login pages:

CrossDomainSSOExample/Login2.png

CrossDomainSSOExample/Login3.png

Traffic info

Same as login.

Great… this seems to be working! How do I set up the sites?

The sample SSO implementation has been developed using Visual Studio 2010, .NET 4.0 Framework, and tested in IIS 7 under a Windows Vista machine. However, it doesn’t use any 4.0 framework specific technology or class library and hence, it can be converted to use for a lower level framework without much effort, if required.

Follow these steps to set up the example SSO implementation in your machine:

    • Download SSO.zip and extract to any convenient location in your PC. The following folders/files will be extracted within the folder “SSO“:

    • Click on “SSO.sln” to open the solution in Visual Studio, to understand “Who is What” in the solution structure:

CrossDomainSSOExample/SolutionExplorer.png

As the names imply:

      • C:\…\www.domain1.com is the website for http://www.domain1.com
      • C:\…\www.domain2.com is the website for http://www.domain2.com
      • C:\…\www.domain3.com is the website for http://www.domain3.com
      • C:\…\www.sso.com is the web site for http://www.sso.com
      • SSOLib is a Class Library that handles all the Single Sign On related logic along with communicating with the SSO site via a Web Service, on behalf of the client sites.
    • Type “inetmgr” in the Run command to launch the IIS Manager (alternatively, you can navigate to the IIS Manager from Start->Program files) and create a site there named “http://www.domain1.com“:

Right click on “Sites” and click on “Add Web Site…“:

CrossDomainSSOExample/AddNewSite.png

Provide the necessary inputs in the following input form and click “OK”:

CrossDomainSSOExample/CreateSiteDomain1.png

The site “http://www.domain1.com” will be created in IIS. After creating the site, the site might be shown with a red cross sign in IIS Explorer, indicating that the site is not started yet (this happens in my IIS in Windows Vista Home Premium). In this case, you need to select the site and click on the Restart icon to make sure it starts (the Restart icon is available in the right-middle portion of the screen in IIS Explorer).

CrossDomainSSOExample/RestartSite.png

CrossDomainSSOExample/CreateAllSites.png

    • Click on the “Application Pool” node in IIS Explorer. The application pool listing will be shown in the right pane, and you will see the application pools for the corresponding sites that you’ve just created in IIS.

Make sure all application pools are running under .NET Framework 4.0 (as the web application has been built in Framework 4.0). To do that, right click on the corresponding application pools (that have the same names as the site names) and select the .NET Framework version in the form:

CrossDomainSSOExample/ApplicationPool.png

    • Edit the Hosts file (C:\Windows\System32\drivers\etc\hosts) so that the site names are mapped to the localhost loopback address (127.0.0.1):
127.0.0.1       localhost
127.0.0.1       www.domain1.com
127.0.0.1       www.domain2.com
127.0.0.1       www.domain3.com
127.0.0.1       www.sso.com

If things are correctly done, you should be able to run the sites and test correctly as shown above. Otherwise, please verify if there is any thing missing or misconfigured by reviewing the steps from the start.

OK, how do I implement SSO for my sites?

Good question. The sample SSO implementation works fine. But, as a developer, you would likely be more interested in how to implement SSO in your ASP.NET sites using the things developed. While implementing the SSO model, I tried to make a pluggable component (SSOLib.dll) so that it requires minimum programmatic change and configuration. Assuming that you have some existing ASP.NET applications, you need the following steps to implement “Single Sign On” across them:

    • Add a reference to “SSOLib.dll“, or add a reference to the “SSOLib” project to each ASP.NET application.
    • Set up the SSO site (see previous steps).
    • Configure your ASP.NET applications to use the SSO site. To do this, just add the following configurations in the web.confing of each ASP.NET application:
<!--Configuration section for SSOLib-->
 <configSections>
    <sectionGroup name="applicationSettings" 
         type="System.Configuration.ApplicationSettingsGroup, System, 
               Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <section name="SSOLib.Properties.Settings" 
        type="System.Configuration.ClientSettingsSection, System, 
              Version=4.0.0.0, Culture=neutral, 
              PublicKeyToken=b77a5c561934e089" 
        requirePermission="false" />
    </sectionGroup>
 </configSections>
 <applicationSettings>
    <SSOLib.Properties.Settings>
     <setting name="SSOLib_Service_AuthService" serializeAs="String">
       <value>http://www.sso.com/AuthService.asmx</value>
     </setting>
    </SSOLib.Properties.Settings>
 </applicationSettings>
 <appSettings>
       <add key="SSO_SITE_URL" 
         value="http://www.sso.com/Authenticate.aspx?ReturnUrl={0}" />
       <add key="LOGIN_URL" value="~/Login.aspx" />
       <add key="DEFAULT_URL" value="~/Page.aspx" />
   </appSettings>
<!--End Configuration section for SSOLib-->

Note: Modify the configuration values according to the SSO site URL of your setup and your application specific needs.

  • Modify the code-behind classes (*.aspx.cs) of the login page and other private pages (pages which are accessible only to authenticated users) so that instead of extending System.Web.UI.Page, they will extend the SSOLib.PrivatePage class.
  • In addition to existing codes that perform the log out functionality, call the Logout() method that is already available to the code-behind classes from the SSOLib.PrivatePage base class. Also, instead of executing existing login functionality, call the Login() method of SSOLib.PrivatePage and execute other post-login codes if they exist already.

That’s it! You should be done with your SSO implementation.

Hold on! My pages already extend a base class

OK, there is a high chance that you already have a base class which is extended by the code-behind classes in your ASP.NET applications. If that is so, integrating the SSOLib.PrivatePage may become even easier for you.

Let’s say there is already a BasePage class which is extended by the code-behind classes of the authenticated page (pages which are accessible onto to the authenticated users) in one of your applications. In this case, instead of modifying the code-behind classes of all the ASPX pages, you might just need to modify theBasePage so that it extends SSOLib.PrivatePage, and you are done.

class BasePage : SSOLib.PrivatePage
{
    ...
}

Another alternative is to modify SSOLib.PrivatePage to extend the existing BasePage (you have the source code, you can do it) and modify all the existing aspx.cs classes of the authenticated pages to extendSSOLib.PrivatePage as suggested. That is:

class PrivatePage : BasePage 
{
    ...
}

If there is any conflicting code or method between the existing BasePage class and the SSOLib.PrivatePageclass, you might need to modify some code in these two classes. It would be preferable not to change the code of SSOLib.PrivatePage unless any bug is discovered, and it would be better to change the existing BasePagecode as required. But, feel free to change the code of SSOLib.PrivatePage if you really need to, it’s all yours!

Hmm.. so who does all the SSO things? Where is the magic?

Good question. In the ideal case, using this example SSO model, you won’t have to write a single line of Sign On oriented code to implement SSO in your ASP.NET applications (except some configuration and inheritance changes). How is this possible? Who is managing all the dirty SSO stuff?

SSOLib and the SSO site are the two magicians doing all the tricks. SSOLib is a DLL which is used by each client site to carry out the following things:

  • Communicate to SSO site via a Web Service.
  • Redirect to the SSO site or login page or serve the requested page.

The following diagram depicts the role of SSOLib in the SSO model:

CrossDomainSSOExample/SSOLib.png

The most important thing inside SSOLib is the PrivatePage class which is inherited by the code-behind pages of the authenticated classes. This class inherits the System.Web.UI.Page class, and overrides the OnLoad()method, as follows:

public class PrivatePage : Page
{
      protected override void OnLoad(EventArgs e)
      {
           //Set caching preferences
           SetCachingPreferences();
                      
           //Read QueryString parameter values
           LoadParameters();

           if (IsPostBack)
           {
               //If this is a postback, do not redirect to SSO site.
               //Rather, hit a web method to the SSO site 
               //to know user's logged in status
               //and proceed based on the status
               HandlePostbackRequest();
               base.OnLoad(e);
               return;
           }

           //If the current request is marked not 
           //to be redirected to SSO site, do not proceed
           if (SessionAPI.RequestRedirectFlag == false)
           {
               SessionAPI.ClearRedirectFlag();
               base.OnLoad(e);
               return;
           }

           if (string.IsNullOrEmpty(RequestId))
           {
               //Absence of Request Paramter "RequestId" means 
               //current request is not redirected from SSO site.
               //So, redirect to SSO site with ReturnUrl
               RedirectToSSOSite();
               return;
           }
           else
           {
               //Current request is redirected from 
               //the SSO site. So, check user status
               //And redirect to appropriate page
               ValidateUserStatusAndRedirect();
           }

           base.OnLoad(e);
       }
}

Basically, OnLoad() is called whenever a Page object is loaded as a result of a URL hit in the browser, and the core SSO logic is implemented inside this method. All the codes is self descriptive and documented to depict what is going on.

More on the SSOLib functionality in the following sections.

What does http://www.sso.com do?

The SSO site has the following two important functionalities:

    • User authentication and user retrieval Web Services which are accessed by the client sites via the SSOLib DLL to authenticate the user and to know the user’s logged-in status on the SSO site. The following services are available:

CrossDomainSSOExample/SSOWebServices.png

    • Setting and retrieving user authentication cookie using an ASPX page (Authenticate.aspx) which is redirected by SSOLib for setting / checking or removing the cookie based upon the type of request.

Following is the core functionality that is performed by Authenticate.aspx. The codes is self-descriptive and documented for easy understandability.

protected void Page_Load(object sender, EventArgs e)
{
   //Read request paramters and populate variables
   LoadRequestParams();

   if (Utility.StringEquals(Action, AppConstants.ParamValues.LOGOUT))
   {
       //A Request paramter value Logout indicates
       //this is a request to log out the current user
       LogoutUser();
       return;
   }
   else
   {
       if (Token != null)
       {
           //Token is present in URL request. That means, 
           //user is authenticated at the client site 
           //using the Login screen and it redirected
           //to the SSO site with the Token in the URL parameter, 
           //so set the Authentication Cookie
           SetAuthCookie();
       }
       else
       {
           //User Token is not available in URL. So, check 
           //whether the authentication Cookie is available in the Request
           HttpCookie AuthCookie = 
              Request.Cookies[AppConstants.Cookie.AUTH_COOKIE];
           if (AuthCookie != null)
           {
               //Authentication Cookie is available 
               //in Request. So, check whether it is expired or not.
               //and redirect to appropriate location based upon the cookie status
               CheckCookie(AuthCookie, ReturnUrl);
           }
           else
           {
               //Authentication Cookie is not available 
               //in the Request. That means, user is logged out of the system
               //So, mark user as being logged out
               MarkUserLoggedOut();
           }
       }
   }
}

Looks pretty good. Did you have to handle any critical issues?

Another good question. The core SSO logic seems pretty straightforward. That is:

If current request is a PostBack, 
    If this is a PostBack in Login page (For Login)
        Do Nothing
    Else
        Do not redirect to SSO site. Rather, invoke a web service at SSO site 
        to know user's logged in status, using the User *Token. 
    If user is not logged out 
        Proceed the normal PostBack operation.
    Else 
        Redirect to login page
Else
    If current request is not redirected from the SSO Site, 
        Redirect it to SSO site with   setting ReturnUrl with 
        the current Request URL and parameters.
    Else
        Get user's Logged in status on SSO Site 
        by invoking a web service with user *Token

        If user is logged out there, 
            Redirect to Login page
        If current request is a page refresh, 
            Redirect to SSO site with ReturnUrl
        Else 
            Redirect to the originally requested URL
    End If
End If

*User token is a hash code of a GUID that identifies a user’s login onto the SSO site uniquely. Each time a user is logged onto the SSO site, the token is generated at the SSO site, and this token is used later to set the authentication cookie and to retrieve the user object by the client sites.

But there are some obvious issues that were needed to be handled to implement the SSO logic. These are marked in bold in the above logic:

Implement “Redirect to login page” and “Redirect to the originally requested URL

SSOLib.PrivatePage redirects to the SSO site, or redirects to the currently requested page at the client site, based upon the situation. But, there is a problem if SSOLib.PrivatePage redirects to a page of the current site. As each authenticated page extends the SSOLib.PrivatePage class, a redirect to a page in the current site from SSOLib.PrivatePage would redirect to itself again and again, and will cause an infinite redirect loop.

To solve this issue, an easy fix could be to add a request parameter (say, Redirect=false) to indicate that the request should not be redirected any further. But, this would allow the user to see the Request parameter and allow the user to “hack” the system by altering its value. So, instead of using a Request parameter, I used a Session variable to stop further redirection, before redirecting to any URL of the current site fromSSOLib.PrivatePage. In OnLoad(), I check the Session variable and reset it and return as follows:

//If the current request is marked not to be redirected to SSO site, do not proceed
if (SessionAPI.RequestRedirectFlag == false)
{
   SessionAPI.ClearRedirectFlag();
   return;
}

Detect whether “Current request is not redirected from the SSO Site“, and whether “current request is a page refresh

SSOLib.PrivatePage redirects to the SSO site for setting or checking the authentication cookie. After the SSO site is done with its work, it redirects back to the calling site using the URL that is set in ReturnUrl.

This also creates a scenario where the client site might again redirect to the SSO site and the SSO site again redirects to client site and creates an infinite redirection loop. Unlike the previous situation, this time, a Session variable could not be used because the redirection is occurring from the SSO site, and the client site and the SSO site have different Session states. So, a Request parameter value should be used to prevent further redirect to the SSO site once the SSO site redirects to the client site.

But again, using a Request parameter to prevent redirection would allow the user to alter it and break the normal functionality. To work-around this, the Request parameter value is set with a hash of a GUID(RequestId=Hash(New GUID)), and this is appended from the SSO site before redirecting back to the client-site URL.

The redirect request executes the OnLoad() method of SSOLib.PrivatePage again, and this time, it finds theRequestId, and this indicates that this request is redirected back from the SSO site and hence this should not be redirected to the SSO site further.

But, what if the user alters the value of the RequestId in the query string and hits the URL, or the user just refreshes the current page?

As each different request is to be redirected to the SSO site (except the postback hits), in this case, this request should be redirected to the SSO site as usual. But, the request URL already contains a RequestId, and despite this, the request should be redirected to the SSO site. So, how should SSOLib.PrivatePage understand this?

There is only one way. A specific RequestId should be valid for each particular redirect from the SSO site only, and once the RequestId is received at the client site from the Request parameter, it should expire instantly so that even if the next URL hit contains the same RequstId, or if the next URL contains an invalid value, it redirects to the SSO site.

The following logic has been used to handle this scenario:

if (string.IsNullOrEmpty(RequestId))
{
   //Absence of Request Paramter RequestId means current 
   //request is not redirected from SSO site.
   //So, redirect to SSO site with ReturnUrl
   RedirectToSSOSite();
   return;
}
else
{
   //Current request is redirected from the SSO site. So, check user status
   //And redirect to appropriate page
   ValidateUserStatusAndRedirect();
}

//And,

UserStatus userStatus = AuthUtil.Instance.GetUserStauts(Token, RequestId);
if (!userStatus.UserLoggedIn)
{
   //User is not logged in at SSO site. So, return the Login page to user
   RedirectToLoginPage();
   return;
}
if (!userStatus.RequestIdValid)
{
   //Current RequestId is not valid. That means, 
   //this is a page refresh and hence, redirect to SSO site
   RedirectToSSOSite();
   return;
}
if (CurrentUser == null || CurrentUser.Token != Token)
{
   //Retrieve the user if the user is not found 
   //in session, or, the current user in session
   //is not the one who is currently logged onto the SSO site
   CurrentUser = AuthUtil.Instance.GetUserByToken(Token);
   if (CurrentUser.Token != Token || CurrentUser == null)
   {
       RedirectToSSOSite();
       return;
   }
}

On the other hand, before redirecting to the client site, the SSO site generates a RequestId, appends it with the query string, and puts it in Application using the RequestId as the key and value. Following is how the SSO site redirects back to the client site:

/// <summary>
/// Append a request ID to the URl and redirect
/// </summary>
/// <param name="Url"></param>
private void Redirect(string Url)
{
    //Generate a new RequestId and append to the Response URL.
    //This is requred so that, the client site can always
    //determine whether the RequestId is originated from the SSO site or not
    string RequestId = Utility.GetGuidHash();
    string redirectUrl = Utility.GetAppendedQueryString(Url, 
              AppConstants.UrlParams.REQUEST_ID, RequestId);
    
    //Save the RequestId in the Application
    Application[RequestId] = RequestId;

    Response.Redirect(redirectUrl);
}

Note that, before redirection, RequestId is stored in the Application scope to mark that this RequestId is valid for this particular response to the client site. Once the client site receives the redirected request, it executes the GetUserStatus() Web Service method, and following is how the GetUserStatus() web method clears the RequestId from the Application scope so that any subsequent requests with the same RequestId or any request with an invalid RequestId can be tracked as an invalid RequestId:

/// <summary>
/// Determines whether the current request is valid or not
/// </summary>
/// <param name="RedirectId"></param>
/// <returns></returns>
[WebMethod]
public UserStatus GetUserStauts(string Token, string RequestId)
{
      UserStatus userStatus = new UserStatus();

      if (!string.IsNullOrEmpty(RequestId))
      {
          if ((string)Application[RequestId] == RequestId)

          {
              Application[RequestId] = null;
              userStatus.RequestIdValid = true;
          }
      }

      userStatus.UserLoggedIn = 
        HttpContext.Current.Application[Token] == null ? false : true;

      return userStatus;
}

Get user’s logged in status on the SSO site

The GetUserStauts() Web Service method returns the user’s status inside a UserStatus object, which has two properties: UserLoggedIn and RequestIdValid.

Once a user is logged onto the SSO site via the Authenticate Web Service method, it generates a User Token (hash code of a new GUID) and stores the user Token inside an Application variable using the Token as the Key:

/// <summary>
/// Authenticates user by UserName and Password
/// </summary>
/// <param name="UserName"></param>
/// <param name="Password"></param>
/// <returns></returns>
[WebMethod]
public WebUser Authenticate(string UserName, string Password)
{
   WebUser user = UserManager.AuthenticateUser(UserName, Password);
   if (user != null)
   {
       //Store the user object in the Application scope, 
       //to mark the user as logged onto the SSO site
       //Along with the cookie, this is a supportive way 
       //to trak user's logged in status
       //In order to track a user as logged onto the SSO site 
       //user token has to be presented in the cookie as well as
       //he/she has to be presented in teh Application scope
       HttpContext.Current.Application[user.Token] = user;
   }
   return user;
}

When the user logs out of the system from any client site, the authentication cookie is removed, and also the user object is removed from the Application scope (inside Authenticate.aspx.cs in the SSO site):

/// <summary>
/// Logs out current user;
/// </summary>
private void LogoutUser()
{
   //This is a logout request. So, remove the authentication Cookie from the response
   if (Token != null)
   {
       HttpCookie Cookie = Request.Cookies[AppConstants.Cookie.AUTH_COOKIE];

       if (Cookie.Value == Token)
       {
           RemoveCookie(Cookie);
       }
   }
   //Also, mark the user at the application scope as null
   Application[Token] = null;

   //Redirect user to the desired location
   //ReturnUrl = GetAppendedQueryString(ReturnUrl, 
   //   AppConstants.UrlParams.ACTION, AppConstants.ParamValues.LOGOUT);
   Redirect(ReturnUrl);
}

So, without redirecting to the SSO site, it is possible to know the user’s logged in status just by checking the user’s presence in the Application scope of the SSO site. The client sites invoke the Web Service method of the SSO site, and the SSO site returns the user’s logged in status inside the UserStatus object.

This method of knowing the user’s logged in status is handy because when a postback occurs, the client sites would not want to redirect to the SSO site (because, if they do that, the postback event methods cannot be executed).

In such cases, they invoke the web method to know the user’s logged in status, and if the user is not available at the SSO site, the current request is redirected to the login page. Otherwise, the normal postback event method is executed.

Wait a minute, storing the user in the Application scope should mark the user logged in for all sessions. How do you handle that?

True. Once a user is authenticated, he/she is stored in the Application scope to mark as logged in. But, theApplication scope is a global scope irrespective of the site and user sessions. So, there is a risk that the user might also get marked as logged in for all browser sessions.

This sounds risky. But, this is handled with care so that the user object of a particular browser session is not available to other browser sessions. Let us now see how this has been handled.

Once a user logs onto the SSO site, the user is stored in the Applicationscope against the user Token, which is valid only for a particular user Login session.

If some direct request is hit in a new window (hence with a new Session) with the user Token (with or without the RequestId) by copying the URL from the address bar, the system will not let the URL request bypass the login screen. Why? Because the authentication cookie that is set by the SSO site is a “non-persistent” cookie, and hence this cookie is sent by the browser to the SSO site only if subsequent requests are hit in the same browser session (from the same browser window or different tabs in the same window). That means, if a new browser window is opened, it does not have any authentication cookie to send to the SSO site, and naturally, the request is redirected to the login page of the client site. So, even if a user is stored in the Application scope in the SSO site, that user object is stored against a different user Token as a key, that can never be accessed for any new request in the new session, because this request does not know about the existing user Token, and once the user logs onto this new browser session, it gets a new user Token which never matches with the existing ones.

How cookie timeout and sliding expiration is maintained?

The web.config of the SSO site has configuration options for configuring the cookie timeout value and for enabling/disabling the sliding expiration of the cookie.

<appSettings>
    <add key="AUTH_COOKIE_TIMEOUT_IN_MINUTES" value="30"/>
    <add key="SLIDING_EXPIRATION" value="true"/>
</appSettings>

The cookie timeout value can be configured in the web.config of the SSO site and the timeout value applies to all client sites under the SSO. That is, if the cookie timeout value is specified in the web.config as 30 minutes and ifuser1 logs onto http://www.domain1.com, the cookie is available for the next 30 minutes in the browser, and henceuser1 is signed on the other two sites for this 30 minutes, unless user1 is logged out of the site.

Now, how is this cookie timeout implemented? Simple, by setting the cookie expiration time, of course.

Unfortunately, I couldn’t do that. Why? Because, by default, when a cookie is set in the Response, it is created as a non-persistent cookie (the cookie is stored only in the browser’s memory for the current session, not in the client’s disk). If the expiry date is specified for the cookie, ASP.NET runtime automatically instructs the browser to store the cookie as a persistent cookie.

In our case, we don’t want to create a persistent cookie, because this will let the other sessions to also send the authentication cookie to the SSO site and eventually mark the user as logged in. We do not want that to happen.

But, the expiration datetime has to be set somehow. So, I stored the expiration value in the cookie’s value, along with appending to the user’s Token, as follows:

/// <summary>
/// Set authentication cookie in Response
/// </summary>
private void SetAuthCookie()
{
   HttpCookie AuthCookie = new HttpCookie(AppConstants.Cookie.AUTH_COOKIE);

   //Set the Cookie's value with Expiry time and Token
   int CookieTimeoutInMinutes = Config.AUTH_COOKIE_TIMEOUT_IN_MINUTES;

   AuthCookie.Value = Utility.BuildCookueValue(Token, CookieTimeoutInMinutes);
   //Appens the Token and expiration DateTime to build cookie value

   Response.Cookies.Add(AuthCookie);

   //Redirect to the original site request
   ReturnUrl = Utility.GetAppendedQueryString(ReturnUrl, 
                        AppConstants.UrlParams.TOKEN, Token);
   Redirect(ReturnUrl);
}

/// <summary>
/// Set cookie value using the token and the expiry date
/// </summary>
/// <param name="Value"></param>
/// <param name="Minutes"></param>
/// <returns></returns>
public static string BuildCookueValue(string Value, int Minutes)
{
    return string.Format("{0}|{1}", Value, 
       DateTime.Now.AddMinutes(Minutes).ToString());
}

Eventually, when the cookie is received at the SSO site, its value is retrieved as follows:

/// <summary>
/// Reads cookie value from the cookie
/// </summary>
/// <param name="cookie"></param>
/// <returns></returns>
public static string GetCookieValue(HttpCookie Cookie)
{
   if (string.IsNullOrEmpty(Cookie.Value))
   {
       return Cookie.Value;
   }
   return Cookie.Value.Substring(0, Cookie.Value.IndexOf("|"));
}

And, the expiration date time is retrieved as follows:

/// <summary>
/// Get cookie expiry date that was set in the cookie value
/// </summary>
/// <param name="cookie"></param>
/// <returns></returns>
public static DateTime GetExpirationDate(HttpCookie Cookie)
{
   if (string.IsNullOrEmpty(Cookie.Value))
   {
       return DateTime.MinValue;
   }
   string strDateTime = 
     Cookie.Value.Substring(Cookie.Value.IndexOf("|") + 1);
   return Convert.ToDateTime(strDateTime);
}

If SLIDING_EXPIRATION is set to true in the web.config, the cookie expiration date-time value is increased with each request, with the minute value specified in AUTH_COOKIE_TIMEOUT_IN_MINUTES in the web.config. The following code does that:

/// <summary>
/// Increases Cookie expiry time
/// </summary>
/// <param name="AuthCookie"></param>
/// <returns></returns>
private HttpCookie IncreaseCookieExpiryTime(HttpCookie AuthCookie)
{
   string Token = Utility.GetCookieValue(AuthCookie);
   DateTime Expirytime = Utility.GetExpirationDate(AuthCookie);
   DateTime IncreasedExpirytime = 
     Expirytime.AddMinutes(Config.AUTH_COOKIE_TIMEOUT_IN_MINUTES);

   Response.Cookies.Remove(AuthCookie.Name);

   HttpCookie NewCookie = new HttpCookie(AuthCookie.Name);
   NewCookie.Value = 
     Utility.BuildCookueValue(Token, Config.AUTH_COOKIE_TIMEOUT_IN_MINUTES);

   Response.Cookies.Add(NewCookie);

   return NewCookie;
}

Can this implementation be used for production systems?

Yes! It surely can be used, but before that, some security and other cross-cutting issues have to be addressed. This is just a basic implementation, and I didn’t verify the model with a professional Quality Assurance process (though I did some basic acceptance testing myself). Also, this authentication does not offer the full flexibility and powers that Forms authentication provides. Additionally, it does not have the built-in authorization mechanism of Forms authentication, and hence you might need to write some more customization on the current SSO implementation, based upon your specific requirements.

However, I’ll try to update the SSO model to enrich it with more features and make it robust so that this could be used in commercial systems without requiring any customization.

Any suggestion or feedback is highly welcome. Adios!

REF:

http://www.codeproject.com/Articles/106439/Single-Sign-On-SSO-for-cross-domain-ASP-NET-applic

http://www.codeproject.com/Articles/114484/Single-Sign-On-SSO-for-cross-domain-ASP-NET-appl

[Photograph] Những nhiếp ảnh gia nổi tiếng thế giới chụp cùng bức ảnh làm họ nổi tiếng

Vào năm 2006, Tim Mantoani – một nhiếp ảnh gia Mỹ – đã thuê một máy ảnh chụp bằng film khổ lớn: chiếc Polaroid 20×24 chụp lại chân dung những nhiếp ảnh gia nổi tiếng thế giới đứng cùng bức ảnh đã giúp họ nổi tiếng. Ông lấy tên bộ ảnh là “Behind Photographs” với ý muốn lưu lại chân dung hiện thời của hơn 150 là tác giả đứng phía sau những bức ảnh mà chúng ta vẫn tôn vinh. Mỗi bức ảnh của họ là một câu chuyện lớn cho nhân loại và chính cuộc đời của họ cũng là những câu chuyện lớn trong giới nhiếp ảnh thế giới.

14 copya.

Chiếc máy mà Tim Mantoani sử dụng để thực hiện bộ ảnh này là chiếc Polaroid 20×24. Đó là chiếc máy ảnh thuộc loại “instant camera” chụp film in hình lấy liền khổ rất lớn: 20 inches x 24 inches do hãng Polaroid sản xuất. Hiện thời, dòng máy này rất hiếm trên thế giới nhưng lại nhiều người thích dùng, và ở Mỹ, người ta thường đến một số Studio hoặc đại lý của Polaroid để thuê dùng trong một dự án ảnh nào mà thôi. Hiện tại, loại máy ảnh “íntant camera” khổ nhỏ có khá nhiều do hãng Fujifilm sản xuất và Polaroid chỉ sản xuất máy khổ lớn nhất là 8×10″ mà thôi.

Steve McCurry – Girl In Afghanistan
Steve McCurry: Peshawar, Pakistan 1984. “Tôi đã tìm cô gái này suốt 17 năm và đã gặp vào năm 2002. Cô ấy tên là Sharbat Gula”.
1.

Jeff Widener – Beijing 1989
2.
Harry Benson – The Beatles
Harry Benson: Brian Epstein – người quản lý Beatles – nói rằng họ là số một ở Mỹ và tôi đã đến với họ ở New York năm 1964.
3.

Lyle Owerko – 9/11
Lyle Owerko: Không ai biết sự êm đẹp sẽ trở nên đau đớn tột cùng cho toàn nhân loại vào một thời khắc nào đó . Bức ảnh này chỉ là một góc nhỏ của toàn bộ sự kiện và của hàng ngàn câu chuyện với hàng triệu con người kể cho nhau nghe. Không có chữ viết nào, âm thanh nào, mùi vị nào và kể cả tiếng nói nào có thể đọng lại trong tâm trí tôi vào ngày hôm đó. Tôi chỉ có thể làm một việc là bấm máy ảnh trong cái ngày 9/11 tang thương ấy. Tôi hy vọng mọi vết thương sẽ được chữa lành, sự khôn ngoan và hoà bình sẽ đẩy lùi bóng tối và hận thù, nhân loại đi về phía trước trong ân sủng và bình an của sự hiểu biết.
4.
Marry Ellen Mark – Ringmaster With Elephant
Marry Ellen Mark: Tôi đang cầm bức hình do tôi chụp Ram Singh Prakash và chú voi yêu quý của anh ta vào năm 1990. Bức ảnh thực hiện tại Ấn Độ là một phần trong toàn bộ dự án ảnh “The Great Golden Circus” của tôi. Tôi yêu Ấn Độ. Singh qua đời vài tháng sau khi bức ảnh này được chụp.
5.
Thomas Mangelsen – Brown bear
Thomas Mangelsen: Ảnh chụp tại vườn quốc gia Brooks Falls Katmai National Park, Alaska tháng 7 năm 1988.
6.
David Doubilet – Circle Of Barracuda
David Doubilet: Circle of Barracuda, New Ireland, Papua New Guinea. 70% hành tinh là đại dương và nơi đó chứa những vẻ đẹp vô hạn đang tiềm ẩn. Nơi đó, ánh sáng chuyển biến rất lạ lùng, dường như không có đường thẳng, như một vòng tròn hoàn hảo. Một thế giới không có những góc cạnh.
7.

May Pang – John Lennon
May Pang: Bức ảnh tôi gọi là “chân dung gia đình” chụp tại Long Island Sound NY. vào năm 1974
8.
Meil Leifer – Ali vs. Liston
Neil Leifer: Ali vs. Liston – May 25, 1965, Lewiston, Mayne
9.
Vincent Laforet – Me And My Human
Vincent Laforet: Tôi may mắn được chiêm ngưỡng những điều tuyệt vời trong sự nghiệp từ hai mươi năm trước. Rất nhiều cảnh đẹp mà với người khác có thể là không; hoặc những khoảnh khắc quan trọng của lịch sử hay sự kiện nào đó trong hành trình cuộc sống. Một trong những điều tôi học được qua nhiếp ảnh trên không là không có một bước lùi chân.
10.
Bob Gruen – John Lennon
Bob Gruen: John Lennon gọi tôi chụp ảnh cho trang bìa của Album ‘Walls + Bridges’ ở miền đông New York. Sau khi chụp loạt ảnh cho trang bìa. Tôi hỏi anh có cái áo t-shirt New York City và chúng tôi thực hiện ảnh này.
11.
Elliott Erwitt – Two Dogs With Owner
Elliott Erwitt: Tôi chụp năm 1974 trên đường phố từ căn hộ của tôi ở Central Park – New York.
12.

Lori Grinker – Mike Tyson
Lori Grinker: Mike Tyson – 1980.
13.
Nick Ut – Napalm Attack In Vietnam
Nick Ut: 8/6/1972 tại Trảng Bàng. Bé gái trong ảnh là Kim Phúc khi ấy 9 tuổi trong đợt bom Napalm.
14.
Herman Leonard – Jazz Musicians
Herman Leonard: Tôi chụp vào đầu năm 1948 tại Royal Roost – NY. Đó là một buổi tập duy nhất mà tôi có thể chụp ảnh những người vĩ đại Jazz.
15.
Douglas Kirkland – Marilyn Monroe
16.
Carl Fischer – Muhammad Ali
Carl Fischer: Muhammad Ali, New York, 1967
17.

Các bạn có thể xem đầy đủ với bản thương mại trên Amazon

Nguồn: boredpanda