page_prerender and owin opened connect


In the earlier article we saw how to amend a simple web forms project, built in modern style from owin components. Rather than use forms auth and WIF to guard authenticated access to a web form, the site uses OWIN cookie middleware and OWIN opened connect middleware (talking to AAD) similarly. The architecture is one in which the site has both a local signin page, activated by the cookie middleware, and an optional opened connect resource that invokes the sending of oauth2 requests and the processing of the grants that come back.

Screenshot (48)

Now, we want to reapply a pattern we learned from a WIF SDK sample. This code lets a resource invoke the signin login page, as a consequence of the interaction of URL authorization and forms authn interceptors. However, the signing page acts as a protocol gateway rather than a credential validator. It invokes SSO requests (albeit WIF style request APIs, in its era). So can we take some of the knowhow, and in OUR projects signin page make it too acts as an openid connect gateway (gatewaying to owing-managed tickets/cookies!)

Well, the interesting bit of the sample is in login page (invoked by forms authn module(), where we see a packaged control deliver the gatewaying component:

Screenshot (49)

A more interesting sample unwraps the component. This we get by installing the templates into visual studio 2015 RC (per advice here), and running the make claims aware web SITE template:

 

Screenshot (50)


protected void Page_PreRender( object sender, EventArgs e )
    {
        string action = Request.QueryString[WSFederationConstants.Parameters.Action];
        try
        {
            if ( action == WSFederationConstants.Actions.SignIn )
            {
                // Process signin request.
                SignInRequestMessage requestMessage = (SignInRequestMessage)WSFederationMessage.CreateFromUri( Request.Url );
                if ( User != null && User.Identity != null && User.Identity.IsAuthenticated )
                {
                    SecurityTokenService sts = new CustomSecurityTokenService( CustomSecurityTokenServiceConfiguration.Current );
                    SignInResponseMessage responseMessage = FederatedPassiveSecurityTokenServiceOperations.ProcessSignInRequest( requestMessage, User, sts );
                    FederatedPassiveSecurityTokenServiceOperations.ProcessSignInResponse( responseMessage, Response );
                }
                else
                {
                    throw new UnauthorizedAccessException();
                }
            }
            else if ( action == WSFederationConstants.Actions.SignOut )
            {
                // Process signout request.
                SignOutRequestMessage requestMessage = (SignOutRequestMessage)WSFederationMessage.CreateFromUri( Request.Url );
                FederatedPassiveSecurityTokenServiceOperations.ProcessSignOutRequest( requestMessage, User, requestMessage.Reply, Response );
            }
            else
            {
                throw new InvalidOperationException(
                    String.Format( CultureInfo.InvariantCulture,
                                   "The action '{0}' (Request.QueryString['{1}']) is unexpected. Expected actions are: '{2}' or '{3}'.",
                                   String.IsNullOrEmpty(action) ? "" : action,
                                   WSFederationConstants.Parameters.Action,
                                   WSFederationConstants.Actions.SignIn,
                                   WSFederationConstants.Actions.SignOut ) );
            }
        }
        catch (System.Threading.ThreadAbortException) { } // Thrown by redirect, safe to ignore
        catch ( Exception exception )
        {
            throw new Exception( "An unexpected error occurred when processing the request. See inner exception for details.", exception );
        }
    }

From the federated website example (Where an FP proxies ws-fedp request to several IDPs), we get other code patterns:


//-----------------------------------------------------------------------------
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
//-----------------------------------------------------------------------------


using System;
using System.Web.UI;
using Microsoft.IdentityModel.Claims;
using Microsoft.IdentityModel.Configuration;
using Microsoft.IdentityModel.Protocols.WSFederation;
using Microsoft.IdentityModel.Protocols.WSTrust;
using Microsoft.IdentityModel.SecurityTokenService;
using Microsoft.IdentityModel.Web;
using System.Threading;

namespace PassiveFlowSTS
{
    public partial class _Default : System.Web.UI.Page
    {
        /// 
        /// Returns whether the user is authenticated or not. 
        /// 
        private bool IsAuthenticatedUser
        {
            get
            {
                return ( ( Page.User ) != null && ( Page.User.Identity ) != null && ( Page.User.Identity.IsAuthenticated ) );
            }
        }


        /// 
        /// We perform WS-Federation passive protocol logic in this method and call out to the appropriate request handlers. 
        /// 
        protected void Page_PreRender( object sender, EventArgs e )
        {
            if ( IsAuthenticatedUser )
            {
                IClaimsIdentity id = (IClaimsIdentity)( (IClaimsPrincipal)Thread.CurrentPrincipal ).Identities[0];

                // Use the WSFederationMessage.CreateFromUri to parse the request and create a SignInRequestMessage object.
                Uri requestUri = new Uri( Request.Url.ToString() + "?" + Request.Params["wctx"] );
                SignInRequestMessage requestMessage = WSFederationMessage.CreateFromUri( requestUri ) as SignInRequestMessage;

                if ( requestMessage != null )
                {
                    // Process the sign in request. 
                    SignInResponseMessage responseMessage = ProcessSignInRequest( requestMessage );
                    // Write the response message. 
                    responseMessage.Context = requestMessage.Context;
                    responseMessage.Write( Page.Response.Output );
                    Response.Flush();
                    Response.End();
                }
                string action = Request.QueryString["wa"];

                if ( action == WSFederationConstants.Actions.SignOut )
                {
                    FederatedAuthentication.SessionAuthenticationModule.CookieHandler.Delete();
                    string redirectUrl = Request.QueryString["wreply"];
                    if ( IsValidReplyUrl( redirectUrl ) )
                    {
                        Response.Redirect( redirectUrl );
                    }
                }   

            }
            // forward to an identity provider
            else
            {
                // Check if 'whr' parameter is specified.
                string identityProviderUri = Request.QueryString["whr"];
                string action = Request.QueryString["wa"];
                                              
                if ( action == WSFederationConstants.Actions.SignIn )
                {
                    if ( String.IsNullOrEmpty( identityProviderUri ) )
                    {
                        // Forward the user to the IdetityProvider selection page.
                        identityProviderUri = "https://localhost/PassiveFPSTS/homeRealmSelectionPage.aspx";
                    }
                    SignInRequestMessage signInMessage = new SignInRequestMessage( new Uri( identityProviderUri ), "https://localhost/PassiveFPSTS/Default.aspx" );
                    signInMessage.Context = Request.QueryString.ToString();
                   Response.Redirect(signInMessage.RequestUrl);
                }                              
            }
        }

        /// 
        /// Validate the URL received in a wreply query string.
        /// 
        /// The desired URL.
        /// true if URL is valid
        /// 
        /// Because the wreply directs the STS to redirect to a supplied value, it can
        /// be used as a tool to obscure redirects to malicious sites. For this reason,
        /// the STS should make some attempt to validate the value of reply request.
        /// 
        /// DO NOT use this sample code ‘as is’ in production code.
        /// This is only sample code. A production site would need to validate the request
        /// against known Urls for the current connection.
        /// 
        bool IsValidReplyUrl( string reply )    
        {
            Uri replyUri;
            if ( Uri.TryCreate( reply, UriKind.Absolute, out replyUri ) )
            {
                if ( replyUri.Host.Equals( "localhost", StringComparison.OrdinalIgnoreCase ) )
                {
                    return true;
                }
            }

            return false;
        }

        /// 
        /// Call the STS to get an appropriate token for a request and build a response.
        /// 
        /// 
        /// The 
        private SignInResponseMessage ProcessSignInRequest( SignInRequestMessage requestMessage )
        {
            // Ensure that the requestMessage has the required wtrealm parameter
            if ( String.IsNullOrEmpty( requestMessage.Realm ) )
            {
                throw new InvalidOperationException( "Missing realm" );
            }

            SecurityTokenServiceConfiguration stsconfig = new SecurityTokenServiceConfiguration( "PassiveFlowSTS" );

            // Create our STS backend
            SecurityTokenService sts = new CustomSecurityTokenService( stsconfig );

            // Create the WS-Federation serializer to process the request and create the response
            WSFederationSerializer federationSerializer = new WSFederationSerializer();

            // Create RST from the request
            RequestSecurityToken request = federationSerializer.CreateRequest( requestMessage, new WSTrustSerializationContext() );

            // Get RSTR from our STS backend
            RequestSecurityTokenResponse response = sts.Issue( (IClaimsPrincipal)Thread.CurrentPrincipal, request );

            // Create Response message from the RSTR
            return new SignInResponseMessage( new Uri( response.ReplyTo ),
                    federationSerializer.GetResponseAsString( response, new WSTrustSerializationContext() ) );

        }

    }
}

Advertisements

About home_pw@msn.com

Computer Programmer who often does network administration with focus on security servers. Very strong in Microsoft Azure cloud!
This entry was posted in coding theory. Bookmark the permalink.