首页资源分类编程语言其他 > Professional.ASP.NET.3.5.Security,Membership,and.Role.Management.with.Csharp.and.VB.pdf

Professional.ASP.NET.3.5.Security,Membership,and.Role.Management.with.Csharp.and.VB.pdf

已有 460505个资源

下载专区


TI最新应用解决方案

工业电子 汽车电子 个人消费电子

文档信息举报收藏

标    签: professionalsecurityandmanagementwith

分    享:

文档简介

Professional.ASP.NET.3.5.Security,Membership,and.Role.Management.with.Csharp.and.VB.pdf

文档预览

Professional ASP.NET 3.5 Security, Membership, and Role Management with C# and VB Professional ASP.NET 3.5 Security, Membership, and Role Management 978-0-470-37930-1 As the first book to address ASP.NET 3.5, AJAX, and IIS 7.0 security from the developer’s point of view, this book begins with a look at the new features of IIS 7.0 and then goes on to focus on IIS 7.0 and ASP.NET 3.5 integration. You’ll walk through a detailed explanation of the request life cycle for an ASP.NET application running on IIS 7.0 under the classic mode, from the moment it enters IIS 7.0 until ASP.NET generates a corresponding response. Professional ASP.NET 3.5 MVC 978-0-470-38461-9 The ASP.NET 3.5 MVC Framework enables Microsoft developers to create dynamic data-driven web sites. Packed with real-world examples, this authoritative guide is written by the Microsoft team behind the technology and uses a real-world sample application using MVC in order to explain the tools and technologies that compliment MVC, such as SubSonic, LINQ, jQuery, and REST. Enhance Your Knowledge Advance Your Career Professional ASP.NET 3.5 AJAX 978-0-470-39217-1 The ASP.NET AJAX toolkit is an excellent way to immediately start using AJAX features in applications in that it offers both excitement and enterprise appeal to developers. Professional ASP.NET 3.5 AJAX explains how you can use these features to build amazing Web sites. Coverage of the client library, the ScriptManager server control, ASP.NET AJAX application services and networking, databases and Web services, testing and debugging, and deploying applications demonstrates how the client and server need to interact in order to produce a better Web application. Professional ASP.NET 3.5 978-0-470-18757-9 Professional ASP.NET 3.5 helps the experienced programmer put the latest ASP.NET technologies into action. Greatly expanded from the original best-selling Professional ASP.NET 2.0, Professional ASP.NET 3.5 covers all the key technologies retained from 2.0 in new depth alongside the hundreds of pages of coverage of the important new 3.5 features. Written by 3 of the most wellknown and influential ASP.NET developers, Professional ASP.NET 3.5 is the book you’ll learn the language from and turn to day after day as you write Web applications. And as always, Professional ASP.NET 3.5 features language examples in the book and in the code download in both C# and VB. Beginning ASP.NET 3.5 978-0-470-18759-3 Imar Spaanjaar’s book for programmers new to ASP.NET 3.5 has been widely praised as a well-organized tome of information written by a Web developer for Web developers. Throughout the book the author works through the steps of creating an actual, fully-functional ASP.NET 3.5 Web site. Each chapter bDuoiwldnslooand saktiBllsoylekmaran.Cedomin the previous sections of the book, allowing the reader to gain confidence working with ASP.NET 3.5 as they progress through the book. Professional ASP.NET 3.5 Security, Membership, and Role Management with C# and VB Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxiii Chapter 1: Introducing IIS 7.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 Chapter 2: IIS 7.0 and ASP.NET Integrated Mode . . . . . . . . . . . . . . . . . . . . . . 29 Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model . . . . . . . . . . 79 Chapter 4: A Matter of Trust . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147 Chapter 5: Configuration System Security. . . . . . . . . . . . . . . . . . . . . . . . . . 223 Chapter 6: Forms Authentication. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 287 Chapter 7: Integrating ASP.NET Security with Classic ASP . . . . . . . . . . . . . . 373 Chapter 8: Session State. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 417 Chapter 9: Security for Pages and Compilation. . . . . . . . . . . . . . . . . . . . . . 449 Chapter 10: The Provider Model. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 469 Chapter 11: Membership. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 519 Chapter 12: SqlMembershipProvider. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 561 Chapter 13: ActiveDirectoryMembership Provider. . . . . . . . . . . . . . . . . . . . . 639 Chapter 14: Role Manager. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 691 Chapter 15: SqlRoleProvider . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 735 Chapter 16: AuthorizationStoreRoleProvider. . . . . . . . . . . . . . . . . . . . . . . . . 763 Chapter 17: Membership and Role Management in ASP.NET AJAX 3.5. . . . . . 791 Chapter 18: Best Practices for Securing ASP.NET Web Applications. . . . . . . 823 Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 879 Download at Boykma.Com Download at Boykma.Com Professional ASP.NET 3.5 Security, Membership, and Role Management with C# and VB Download at Boykma.Com Download at Boykma.Com Professional ASP.NET 3.5 Security, Membership, and Role Management with C# and VB Bilal Haidar Stefan Schackow Download at Boykma.Com Professional ASP.NET 3.5 Security, Membership, and Role Management with C# and VB Published by Wiley Publishing, Inc. 10475 Crosspoint Boulevard Indianapolis, IN 46256 www.wiley.com Copyright © 2009 by Wiley Publishing, Inc., Indianapolis, Indiana Portions based on the previous work Professional ASP.NET 2.0 Security, Membership, and Role Management, by Stefan Schackow, copyright © 2006 Stefan Schackow, published by Wiley Publishing, Inc. Published simultaneously in Canada ISBN: 978-0-470-37930-1 Manufactured in the United States of America 10 9 8 7 6 5 4 3 2 1 Library of Congress Cataloging-in-Publication Data Haidar, Bilal. Professional ASP.NET 3.5 security, membership, and role management with C# and VB / Bilal Haidar, Stefan Schackow. p. cm. Includes index. ISBN 978-0-470-37930-1 (paper/website) 1. Active server pages. 2. Microsoft .NET. 3. Computer security. 4. Web site development. I. Schackow, Stefan, 1970- II. Title. QA76.9.A25H344 2008 005.8—dc22 2008036129 No part of this publication may be reproduced, stored in a retrieval system or transmitted in any form or by any means, electronic, mechanical, photocopying, recording, scanning or otherwise, except as permitted under Sections 107 or 108 of the 1976 United States Copyright Act, without either the prior written permission of the Publisher, or authorization through payment of the appropriate per-copy fee to the Copyright Clearance Center, 222 Rosewood Drive, Danvers, MA 01923, (978) 750-8400, fax (978) 646-8600. Requests to the Publisher for permission should be addressed to the Legal Department, Wiley Publishing, Inc., 10475 Crosspoint Blvd., Indianapolis, IN 46256, (317) 572-3447, fax (317) 572-4355, or online at http://www.wiley.com/go/ permissions. Limit of Liability/Disclaimer of Warranty: The publisher and the author make no representations or warranties with respect to the accuracy or completeness of the contents of this work and specifically disclaim all warranties, including without limitation warranties of fitness for a particular purpose. No warranty may be created or extended by sales or promotional materials. The advice and strategies contained herein may not be suitable for every situation. This work is sold with the understanding that the publisher is not engaged in rendering legal, accounting, or other professional services. If professional assistance is required, the services of a competent professional person should be sought. Neither the publisher nor the author shall be liable for damages arising herefrom. The fact that an organization or Web site is referred to in this work as a citation and/or a potential source of further information does not mean that the author or the publisher endorses the information the organization or Web site may provide or recommendations it may make. Further, readers should be aware that Internet Web sites listed in this work may have changed or disappeared between when this work was written and when it is read. For general information on our other products and services please contact our Customer Care Department within the United States at (877) 762-2974, outside the United States at (317) 572-3993 or fax (317) 572-4002. Trademarks: Wiley, the Wiley logo, Wrox, the Wrox logo, Wrox Programmer to Programmer, and related trade dress are trademarks or registered trademarks of John Wiley & Sons, Inc. and/or its affiliates, in the United States and other countries, and may not be used without written permission. All other trademarks are the property of their respective owners. Wiley Publishing, Inc., is not associated with any product or vendor mentioned in this book. Wiley also publishes its books in a variety of electronic formats. Some content that appears in print may not be available in electronic books. Download at Boykma.Com About the Author Bilal Haidar has a BE in Computer Engineering and a BS in Computer Science with a minor in Mathematics from the Lebanese American University (LAU). He has authored several online articles for www.aspalliance.com, www.code-magazine.com, and www.aspnetpro.com, and is one of the top posters at the ASP.NET forums. Bilal has been a Microsoft MVP in ASP.NET since 2004, as well as a Microsoft Certified Trainer, and currently works as a senior developer for Consolidated Contractors Company (CCC), a multinational company whose headquarters are based in Athens, Greece (www.ccc.gr). Bilal runs his own blog, where he shares his technical experience and can be reached at http://www.bhaidar.net. About the Previous Author Stefan Schackow is a Program Manager on the Web Platform and Tools Team at Microsoft. During the Visual Studio 2005 cycle, he worked on the new application services stack in Visual Studio 2005 and owned the Membership, Role Manager, Profile, Personalization and Site Navigation features in ASP.NET 2.0. He also worked on features for Microsoft’s ASP.NET hosting solution. Currently, Stefan is working and speaking on Silverlight for Microsoft. He is a frequent speaker at Microsoft developer conferences. Prior to joining the ASP.NET team, Stefan worked as an application development consultant in Microsoft Consulting Services (MCS) with enterprise customers. Download at Boykma.Com Download at Boykma.Com Acquisitions Director Jim Minatel Development Editors John Sleeva Gus Miklos Technical Editor Alexei Gorkov Production Editor Kathleen Wisor Copy Editor Christopher M. Jones Editorial Manager Mary Beth Wakefield Production Manager Tim Tate Credits Vice President and Executive Group Publisher Richard Swadley Vice President and Executive Publisher Joseph B. Wikert Project Coordinator, Cover Lynsey Stanford Compositor James D. Kramer, Happenstance Type-O-Rama Proofreader Publication Services, Inc. Indexer Jack Lewis Download at Boykma.Com Download at Boykma.Com Acknowledgments The idea of working on this book started when Jim Minatel, Acquisitions Director at Wrox, emailed me about updating the previous version of this book. Despite the fact that I have been publishing articles for magazines and online websites for the past few years, I felt the experience of working on such a book would be really interesting and unique. Only the days later proved me right and made me proud that I accepted Jim’s offer. I spent many hours researching new features and upgrades, writing down everything I learned so that I could share it with you. Many people supported me and provided me with valuable information, including Scott Guthrie, Billy Hoffman, Mike Volodarsky, Steve Scofield, and Anil Ruia. (I apologize if I forgot anyone!) I want to thank the Wiley publishing family, including Jim Minatel, John Sleeva, Gus Miklos, Carol Kessel, Katie Wisor, and Ashley Zurcher, as well as technical editor Alexei Gorkov. I cannot forget the support and flexibility that my company, CCC, represented by my managers and colleagues, showed me during all the stages of writing this book. Your support and understanding gave me enough strength to carry on and finish this book. Finally, a special thanks to my parents and brother and sister, who followed up with me from the beginning of this work and were even more excited about this book than I myself was. Download at Boykma.Com Download at Boykma.Com Contents Introduction Chapter 1: Introducing IIS 7.0 Overview of IIS 7.0 Modular Architecture Deployment and Configuration Management Improved Administration ASP.NET Integration Security Improvements Troubleshooting Improvements Application Pools Integrated Mode Classic Mode IIS 7.0 Components Protocol Listeners World Wide Web Publishing Service Windows Process Activation Service IIS 7.0 Modules Unmanaged Modules Managed Modules Summary Chapter 2: IIS 7.0 and ASP.NET Integrated Mode Advantages of IIS 7.0 and ASP.NET Integrated Mode IIS 7.0 Integrated Mode Architecture system.webServer Configuration Section Group Migrating ASP.NET Applications to Integrated Mode Extending IIS 7.0 with Managed Handlers and Modules Summary Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model Built-in IUSR Account and IIS_IUSRS Group Download at Boykma.Com xxiii 1 2 2 4 6 9 11 12 17 18 18 19 19 19 20 22 22 25 26 29 30 31 34 42 49 77 79 80 Contents Integrated Mode Per-Request Security Where Is the Security Identity for a Request? Establishing the Operating System Thread Identity The Unified Processing Pipeline Thread Identity and Asynchronous Pipeline Events AuthenticateRequest DefaultAuthentication and Thread.CurrentPrincipal PostAuthenticateRequest AuthorizeRequest PostAuthorizeRequest Through PreRequestHandlerExecute Blocking Requests at the IIS Level Identity during Asynchronous Page Execution EndRequest Summary Chapter 4: A Matter of Trust What Is an ASP.NET Trust Level? Configuring Trust Levels Anatomy of a Trust Level A Second Look at a Trust Level in Action Creating a Custom Trust Level Additional Trust Level Customizations LINQ in Medium/Partial Trust ASP.NET Applications The Default Security Permissions Defined by ASP.NET Advanced Topics on Partial Trust Summary Chapter 5: Configuration System Security Using the Element The Path Attribute The allowOverride Attribute Using the lockAttributes Locking Attributes Locking Elements Locking Provider Definitions Managing IIS 7.0 Configuration versus ASP.NET Configuration Extending IIS 7.0 with Managed Modules and Handlers Managing the Native versus Managed Configuration Systems IIS 7.0 Feature Delegation xiv Download at Boykma.Com 81 87 92 98 100 110 117 120 122 135 135 137 143 144 147 148 150 155 162 167 171 179 181 195 221 223 223 225 226 227 227 229 231 233 236 236 238 Reading and Writing Configuration Permissions Required for Reading Local Configuration Permissions Required for Writing Local Configuration Permissions Required for Remote Editing Using Configuration in Partial Trust The requirePermission Attribute Demanding Permissions from a Configuration Class FileIOPermission and the Design-Time API Protected Configuration What Can’t You Protect? Selecting a Protected Configuration Provider Defining Protected Configuration Providers DpapiProtectedConfigurationProvider RsaProtectedConfigurationProvider aspnet_regiis Options Using Protected Configuration Providers in Partial Trust Redirecting Configuration with a Custom Provider Summary Chapter 6: Forms Authentication A Quick Recap of Forms Authentication Understanding Persistent Tickets How Forms Authentication Enforces Expiration Securing the Ticket on the Wire How Secure Are Signed Tickets? Encryption Options in ASP.NET 2.0 and 3.5 Setting Cookie-Specific Security Options requireSSL HttpOnly Cookies slidingExpiration Using Cookieless Forms Authentication Cookieless Options Replay Attacks with Cookieless Tickets The Cookieless Ticket and Other URLs in Pages Payload Size with Cookieless Tickets Unexpected Redirect Behavior Configuring Forms Authentication Inside IIS 7.0 Sharing Tickets between 1.1 and 2.0/3.5 Using Forms Authentication Across Different Content Types Leveraging the UserData Property Download at Boykma.Com Contents 244 247 249 251 253 255 257 258 259 260 261 264 265 267 273 274 278 285 287 288 288 291 295 295 299 303 303 306 308 308 310 315 317 319 322 323 324 326 329 xv Contents Passing Tickets Across Applications Cookie Domain Cross-Application Sharing of Ticket Enforcing Single Logons and Logouts Enforcing a Single Logon Enforcing a Logout Summary Chapter 7: Integrating ASP.NET Security with Classic ASP IIS 5 ISAPI Extension Behavior IIS 7.0 Wildcard Mappings Configuring a Wildcard Mapping The Resource Type Setting DefaultHttpHandler Using the DefaultHttpHandler Serving Classic ASP in IIS 7.0 Integration Mode Authenticating Classic ASP with ASP.NET Will Cookieless Forms Authentication Work? Passing Data to ASP from ASP.NET Passing Username to ASP Authenticating Classic ASP with IIS 7.0 Integrated Mode Authorizing Classic ASP with ASP.NET Passing User Roles to Classic ASP Safely Passing Sensitive Data to Classic ASP Full Code Listing of the Hash Helper Authorizing Classic ASP with IIS 7.0 Integrated Mode Passing Data from ASP.NET to Classic ASP in IIS 7.0 Integrated Mode Summary Chapter 8: Session State Does Session State Equal Logon Session? Session Data Partitioning Cookie-Based Sessions Sharing Cookies Across Applications Protecting Session Cookies Session ID Reuse Cookieless Sessions Configuring Session State Inside IIS 7.0 Session State for Applications Running in IIS 7.0 Integrated Mode Session ID Reuse and Expired Sessions Session ID Denial-of-Service Attacks xvi Download at Boykma.Com 332 332 333 358 359 368 372 373 374 375 376 382 383 384 387 389 391 392 394 394 396 397 398 407 410 411 414 417 417 420 421 422 423 424 424 426 427 435 437 Trust Levels and Session State Serialization and Deserialization Requirements Database Security for SQL Session State Security Options for the OOP State Server Summary Chapter 9: Security for Pages and Compilation Request Validation and Viewstate Protection Request Validation Securing viewstate Page Compilation Fraudulent Postbacks Site Navigation Security Summary Chapter 10: The Provider Model Why Have Providers? Patterns Found in the Provider Model The Strategy Pattern Factory Method The Singleton Pattern Façade Core Provider Classes System.Configuration.Provider Classes System.Web.Configuration Classes System.Configuration Classes Building a Provider-Based Feature Summary Chapter 11: Membership The Membership Class The MembershipUser Class Extending MembershipUser MembershipUser State After Updates Why Are Only Certain Properties Updatable? DateTime Assumptions The MembershipProvider Base Class Basic Configuration User Creation and User Updates Retrieving Data for a Single User Download at Boykma.Com Contents 439 441 445 447 447 449 449 450 451 454 458 462 468 469 469 472 472 474 481 482 484 484 489 490 495 518 519 520 523 526 529 534 536 537 541 541 544 xvii Contents Retrieving and Searching for Multiple Users Validating User Credentials Supporting Self-Service Password Reset or Retrieval Tracking Online Users General Error-Handling Approaches The “Primary Key” for Membership Supported Environments Using Custom Hash Algorithms Summary Chapter 12: SqlMembershipProvider Understanding the Common Database Schema Storing Application Name The Common Users Table Versioning Provider Schemas Querying Common Tables with Views Linking Custom Features to User Records Why Are There Calls to the LOWER Function? The Membership Database Schema SQL Server-Specific Provider Configuration Options Working with SQL Server Express Sharing Issues with SSE Changing the SSE Connection String Database Security Database Schemas and the DBO User Changing Password Formats Custom Password Generation Implementing Custom Encryption Enforcing Custom Password Strength Rules Hooking the ValidatePassword Event Implementing Password History Account Lockouts Implementing Automatic Unlocking Supporting Dynamic Applications Managing an Application’s Users Through IIS 7.0 Summary Chapter 13: ActiveDirectoryMembershipProvider Supported Directory Architectures xviii Download at Boykma.Com 545 545 547 549 550 552 554 557 560 561 562 562 563 566 568 569 572 573 576 577 582 583 584 586 588 590 594 598 600 602 618 621 626 632 637 639 640 Provider Configuration Directory Connection Settings Directory Schema Mappings Provider Settings for Search MembershipProvider Settings Unique Aspects of Provider Functionality ActiveDirectoryMembershipUser IsApproved and IsLockedOut Using the ProviderUserKey Property Working with Active Directory UPNs and SAM Account Names Container Nesting Securing Containers Configuring Self-Service Password Reset Using ADLDS Installing ADLDS with an Application Partition Using the Application Partition Using the Provider in Partial Trust Summary Chapter 14: Role Manager The Roles Class The RolePrincipal Class The RoleManagerModule PostAuthenticateRequest EndRequest Role Cache Cookie Settings and Behavior Working with Multiple Providers during GetRoles RoleProvider Basic Configuration Authorization Methods Managing Roles and Role Associations WindowsTokenRoleProvider Summary Chapter 15: SqlRoleProvider SqlRoleProvider Database Schema SQL Server-Specific Provider Configuration Options Transaction Behavior Download at Boykma.Com Contents 642 642 645 648 649 651 654 655 655 657 659 660 662 667 675 677 682 685 690 691 692 695 707 707 711 712 714 722 724 724 725 726 733 735 735 737 738 xix Contents Provider Security Trust-Level Requirements and Configuration Database Security Working with Windows Authentication Running with a Limited Set of Roles Authorizing with Roles in the Data Layer Supporting Dynamic Applications Managing an Application’s Roles Through IIS 7.0 Summary Chapter 16: AuthorizationStoreRoleProvider Provider Design Supported Functionality Using a File-Based Policy Store Using a Directory-Based Policy Store Using a Microsoft SQL Server Database-Based Policy Store Working in Partial Trust Using Membership and Role Manager Together Summary Chapter 17: Membership and Role Management in ASP.NET AJAX 3.5 ASP.NET Membership and Role Services Overview ASP.NET Membership ASP.NET Role Management ASP.NET AJAX Application Services Enabling ASP.NET Applications with ASP.NET AJAX 3.5 Enabling ASP.NET Application Services AuthenticationServiceManager and RoleServiceManager Classes Authentication Service Role Service Summary Chapter 18: Best Practices for Securing ASP.NET Web Applications Web Application Security Threats Overview Developers Beware Know Your Users Run Applications with Minimum Privileges Validate User Input Secure Cookies xx Download at Boykma.Com 739 739 745 746 748 755 757 758 760 763 763 766 768 771 780 783 786 789 791 792 792 794 796 796 801 803 804 816 822 823 824 827 827 829 829 838 Secure Database Access SQL Injection Attacks Cross-Site Scripting Cross-Site Request Forgery Handle Exceptions Properly Guard Against Denial-of-Service Threats Secure Data Transmission AJAX-Enabled Application Threats Information Leakage JSON Hijacking Amplified Cross-Site Scripting Summary Index Contents 841 849 853 857 861 866 872 872 872 874 876 878 879 Download at Boykma.Com xxi Download at Boykma.Com Introduction This book covers security topics on a wide range of areas in ASP.NET 2.0 and ASP.NET 3.5. It starts with an introduction to Internet Information Services 7.0 (IIS 7.0) and then explains in detail the new IIS 7.0 Integrated mode of execution. Next is detailed coverage of how security is applied when an ASP.NET application starts up and when a request is processed in the newly introduced integrated request-processing pipeline. The book then branches out to cover security information for features such as trust levels, forms authentication, session state, page security, and configuration system security. You will also see how you can benefit from the IIS 7.0 Integrated mode to make use of ASP.NET features to handle non-managed or native requests such as classic ASP due to the fact that ASP.NET and IIS 7.0 join efforts to form an integrated request-processing pipeline to handle requests. Over the course of these topics, you will gain a solid understanding of many of the less publicized security features in ASP.NET 2.0 and ASP.NET 3.5. The book switches gears in Chapter 10 to address two security services in ASP.NET 2.0 and ASP.NET 3.5: Membership and Role Manager. You start out learning about the provider model that underlies both of these features. Then you get a detailed look at the internals of both features, as well as the SQL- and Active Directory-based providers included with them. After reading through these topics, you will have a thorough background on how you can work with those providers and how you can extend them in your applications. The discussion about the ASP.NET features continues, with Chapter 17 dedicated to the ASP.NET AJAX 3.5 security integration with ASP.NET 3.5, showing how to authenticate/authorize users with JavaScript code written on the client-side. Finally, the book closes with a chapter on the best practices ASP.NET developers should follow to protect their ASP.NET applications from malicious attacks. Who This Book Is For This book is intended for developers who already have a solid understanding of ASP.NET 1.1 and ASP.NET 2.0 security concepts in the area of forms authentication, page security, and website authorization. Where the book addresses functionality such as Membership and Role Manager, it assumes that you have already used these features and have a good understanding of the general functionality provided by both of them. It is also assumed that you have already worked with ASP.NET AJAX 3.5. This book does not rehash widely available public information on various features or API reference documentation. Instead, you will find that the book has been written to “peel back the covers” of various ASP.NET security features so that you can gain a much deeper understanding of the security options available to you. The book focuses on explaining the new IIS 7.0 and its Integrated mode of execution, showing the importance of this new mode and how ASP.NET applications benefit from it. The book also addresses lesser known security functionality such as ASP.NET trust levels so that you can take advantage of these approaches in your own applications. If you are looking for an overview on IIS 7.0 and its unified/integrated request-processing pipeline, you will find Chapters 1 and 2 useful. If you are seeking a deep dive on general ASP.NET 2.0 and ASP.NET 3.5 Download at Boykma.Com Introduction security, you will find Chapters 2-9 useful. If your initial focus is on the Membership and Role Manager features, Chapters 10-15 will be immediately useful to you. Chapter 17 focuses on explaining the authentication/​authorization features in ASP.NET AJAX 3.5 to show you how to benefit from some of ASP.NET security features from the client-side JavaScript code, thereby developing more responsive but more secure applications without reinventing the wheel. Finally, Chapter 18 covers a number of threats and attacks that ASP.NET applications might face and provides solutions and on how to handle such threats. After you have read through these topics, you will have a thorough understanding of why ASP.NET security works the way it does, and you will have insights into just how far you can “stretch” ASP.NET 2.0 and ASP.NET 3.5 to match your application’s security requirements. What This Book Covers The subject of ASP.NET security can refer to a lot of different concepts: security features, best coding practices, lockdown procedures, and so on. This book addresses ASP.NET security features from the developer’s point of view. It gives you detailed information on every major area of ASP.NET security you will encounter while developing web applications. And it shows you how you can extend or modify these features. ❑❑ Chapter 1, “Introducing IIS 7.0,” starts by refreshing the ideas on application pools and worker processes before diving into explaining the major components that constitute IIS 7.0. The new modular architecture in IIS 7.0 is explained and a list of both native and managed modules is provided. At the end of the chapter you will learn about the two modes of processing inside IIS 7.0: Integrated and Classic. ❑❑ Chapter 2, “IIS 7.0 and ASP.NET Integrated Mode,” starts by introducing the advantages of using the IIS 7.0 and ASP.NET integrated mode. The discussion expands into exploring the internals and architecture of the new integrated mode of execution. In addition, the chapter highlights the migration problems that a developer or administrator faces when upgrading an application to run inside IIS 7.0 under the integrated mode. The chapter ends with a section on extending the IIS 7.0 infrastructure by developing managed HttpHandlers and HttpModules and installing these features from inside the application’s web.config configuration file without the need to have access to the IIS 7.0 Manager tool. ❑❑ Chapter 3, “HTTP Request Processing in IIS 7.0 Integrate Model,” starts by introducing the new built-in IUSR account and IIS_IUSRS group inside IIS 7.0. It then gives you a detailed walkthrough of the security processing that both IIS 7.0 and ASP.NET perform in the integrated/unified request-processing pipeline. The unified processing pipeline and all its events and stages are introduced with a detailed focus on some of the important stages. You will also see how the default authentication and authorization modules work, as well as the new techniques at the IIS 7.0 level to block access to content based on new IIS 7.0 configuration settings. A section is dedicated to the new native UrlAuthorizationModule that ships as part of the native modules in IIS 7.0. This chapter also describes subtleties in how request identity works with ASP.NET 2.0’s and ASP.NET 3.5’s asynchronous pipeline events and asynchronous page model. ❑❑ Chapter 4, “A Matter of Trust,” describes what an ASP.NET trust level is, and how ASP.NET trust levels work to provide more secure environments for running web applications. The chapter goes into detail on how you can customize trust levels and how to write privileged code that works in partial trust applications. xxiv Download at Boykma.Com Introduction ❑❑ Chapter 5, “Configuration System Security,” covers the security features in the 2.0 and 3.5 Frameworks’ configuration systems. It discusses the configuration options for locking down configuration sections as well as protecting configuration sections from prying eyes. The chapter discusses managing the IIS 7.0 configuration system versus the ASP.NET configuration system, and introduces IIS 7.0 feature delegation, which enables administrators to specify which IIS 7.0 configuration sections ASP.NET applications can change and modify. It also discusses how ASP.NET trust levels and configuration system security work together. ❑❑ Chapter 6, “Forms Authentication,” explains ASP.NET 2.0 and ASP.NET 3.5 features for forms authentication. You will learn about the integrated cookieless support and the support forms authentication has for passing authentication tickets across web applications. The chapter also presents an extensive example of implementing a lightweight single sign on solution using forms authentication, as well as how to enforce a single login using a combination of forms authentication and Membership. ❑❑ Chapter 7, “Integrating ASP.NET Security with Classic ASP,” demonstrates using IIS 7.0 wildcard mappings and ASP.NET 2.0’s and ASP.NET 3.5’s support for wildcard mappings to share authentication and authorization information with Classic ASP applications when an ASP.NET application is operating in the IIS 7.0 Classic mode. The chapter shows how easy it is to integrate ASP.NET security with Classic ASP or any other non-managed content through the Integrated mode of processing introduced with IIS 7.0. The chapter ends with a detailed discussion on authenticating and authorizing classic ASP Content through ASP.NET Membership and Role Manager in an application operating under the IIS 7.0 Integrated mode. ❑❑ Chapter 8, “Session State,” covers security features and guidance for session state. Session state security features in ASP.NET 2.0 and ASP.NET 3.5 are covered, as well as security options for out-of-process state and the effect ASP.NET trust levels have on the session state feature. In addition is a detailed discussion on how to enable session state for non-managed content when ASP.NET applications are operating under the IIS 7.0 Integrated mode. ❑❑ Chapter 9, “Security for Pages and Compilation,” describes some lesser known page security features from ASP.NET 1.1. It also describes ASP.NET 2.0 and ASP.NET 3.5 options for securing viewstate and postback events. Chapter 9 also covers how the dynamic compilation model in ASP.NET 3.5, originally introduced with ASP.NET 2.0, can be used with code access security. ❑❑ Chapter 10, “The Provider Model,” gives you an architectural overview of the provider model in both ASP.NET 2.0 and ASP.NET 3.5. The chapter covers the various Framework classes that are “the provider model,” along with sample code showing you how to write your own custom provider-based features. ❑❑ Chapter 11, “Membership,” talks about the Membership feature in ASP.NET 2.0 and ASP.NET 3.5. The chapter goes into detail about the core classes of the Membership feature as well as how you can extend the feature with custom hash algorithms. ❑❑ Chapter 12, “SqlMembershipProvider,” delves into both the SqlMembershipProvider as well as general database design assumptions that are baked into all of ASP.NET 2.0’s and ASP.NET 3.5’s SQL-based features. You will learn how you can extend the provider to support automatically unlocking user accounts. The sample code also covers custom password encryption, storing password histories, and extending the provider to work in portal environments. ❑❑ Chapter 13, “ActiveDirectoryMembershipProvider,” covers the other membership provider that ships in ASP.NET 2.0 and ASP.NET 3.5 —­ ActiveDirectoryMembershipProvider. You will learn about how this provider maps its functionality onto Active Directory, and you will see how to set up both Active Directory and Active Directory Lightweight Directory Service (introduced with Windows Server 2008) servers to work with the provider. Download at Boykma.Com xxv Introduction ❑❑ Chapter 14, “Role Manager,” describes the Role Manager feature that provides built-in authorization support for ASP.NET 2.0 and ASP.NET 3.5. You will learn about the core classes in Role Manager. The chapter also details how the RoleManagerModule is able to automatically set up a principal for downstream authorization and how the module and Role Manager’s caching work hand in hand. Chapter 14 also covers the WindowsTokenRoleProvider, one of the providers that ships with Role Manager. ❑❑ Chapter 15, “SqlRoleProvider,” discusses the SqlRoleProvider and its underlying SQL schema. You will learn about using the provider in conjunction with Windows authentication, extending the provider to support custom authorization logic, and how you can use its database schema for data layer authorization logic. Although not specific to just SqlRoleProvider, the chapter covers how to get the provider working in a partial trust non-ASP.NET environment. ❑❑ Chapter 16, “AuthorizationStoreRoleProvider,” covers the AuthorizationStoreRoleProvider, a provider that maps Role Manager functionality to the Authorization Manager feature that first shipped in Windows Server 2003 and is now part of Windows Server 2008. You will learn how to set up and use both file-based and directory-based policy stores with the provider. The chapter covers special Authorization Manager functionality that is supported by the provider, as well as how to use both the ActiveDirectoryMembershipProvider and Authorization​ StoreRoleProvider to provide Active Directory-based authentication and authorization in your web applications. ❑❑ Chapter 17, “Membership and Role Management in ASP.NET AJAX 3.5,” discusses how ASP.NET AJAX 3.5 integrates with ASP.NET 3.5 Membership and Role management features through newly introduced web services that act as an interface to the ASP.NET application services. The chapter starts by recapping the Membership and Role Management features in ASP.NET 2.0 and ASP.NET 3.5. The discussion then moves to the steps required to enable existing ASP.NET applications with ASP.NET AJAX 3.5 and then how to enable client-side authentication and role services in the application. Chapter 17 ends by dissecting the authentication and role services in ASP.NET AJAX by detailing all the server-side and client-side classes that make the ASP.NET AJAX 3.5 integration with the ASP.NET application services possible. ❑❑ Chapter 18, “Best Practices for Securing ASP.NET Web Applications,” covers the best practices that can be followed to secure ASP.NET applications. The discussion takes the form of a list of best practices that you can follow and apply in your web application. Each recommended best practice is explained in detail, with a sample code included when possible. The chapter ends by introducing you to the vulnerabilities exposed by introducing AJAX techniques into your applications, and the possible best practices in securing such applications. What You Need to Use This Book This book was written using the .NET 3.5 Framework together with .NET 3.5 Framework SP1 on both Windows Server 2008 and Windows Vista. The sample code in the book has been verified to work with .NET 3.5 Framework and .NET 3.5 Framework SP1 on Windows Vista. To run all of the samples in the book, you will need the following: ❑❑ Windows Server 2008 or Windows Vista ❑❑ Internet Information Services 7.0 (IIS 7.0) xxvi Download at Boykma.Com Introduction ❑❑ Visual Studio 2008 RTM ❑❑ Either SQL Server 2000 or SQL Server 2005 ❑❑ A Windows Server 2008 domain running at Windows Server 2008 functional level Most of the samples should also work when using Windows Server 2008, as Windows Server 2008 and Windows Vista both share the same IIS 7.0. Note that the information in most of the book refers to security credential configuration using IIS 7.0 application pools. Note that all of the book’s chapters require you to have IIS 7.0 installed. Chapters 12 and 15 use the SQL-based providers. You should have either SQL Server 2000 or SQL Server 2005 setup to use these samples. Scattered throughout the book are other samples that rely on the Membership feature. These samples also require either SQL Server 2000 or SQL Server 2005. To run the samples in Chapter 13, you will need either a Windows Server 2008 domain controller or a machine running Active Directory Lightweight Directory Service (ADLDS) or Application Mode (ADAM). Chapter 13 addresses using the ActiveDirectoryMembershipProvider in both Active Directory and ADLDS environments. The sample code in Chapter 16 uses the Authorization Manager functionality in Windows Server 2008 (both setting up policies and consuming them). As a result, to run most of the samples, you will need a Windows Server 2008 domain controller that has been set up to work with Authorization Manager. For file-based policy stores, you do not need your own domain controller if you just want to try out filebased policy stores with the AuthorizationStoreRoleProvider. In addition, Windows Server 2008 enriches the Authorization Manager with the ability to store the authorization information in a Microsoft SQL Server. Therefore, either SQL Server 2000 or SQL Server 2005 is required to show how this new feature works on Windows Server 2008. Conventions To help you get the most from the text and keep track of what’s happening, we’ve used a number of conventions throughout the book. Boxes like this one hold important, not-to-be forgotten information that is directly relevant to the surrounding text. Notes, tips, hints, tricks, and asides to the current discussion are offset and placed in italics like this. As for styles in the text: ❑❑ We highlight new terms and important words when we introduce them. ❑❑ We show keyboard strokes like this: Ctrl+A. Download at Boykma.Com xxvii Introduction ❑❑ We show file names, URLs, and code within the text like so: persistence.properties. ❑❑ We present code in two different ways: We use a monofont type with no highlighting for most code examples. We use gray highlighting to emphasize code that’s particularly important in the present context. Source Code As you work through the examples in this book, you may choose either to type in all the code manually or to use the source code files that accompany the book. All of the source code used in this book is available for download at http://www.wrox.com. Once at the site, simply locate the book’s title (either by using the Search box or by using one of the title lists) and click the Download Code link on the book’s detail page to obtain all the source code for the book. Because many books have similar titles, you may find it easiest to search by ISBN; this book’s ISBN is 978-0-470-37930-1. Once you download the code, just decompress it with your favorite compression tool. Alternately, you can go to the main Wrox code download page at http://www.wrox.com/dynamic/books/download. aspx to see the code available for this book and all other Wrox books. Errata We make every effort to ensure that there are no errors in the text or in the code. However, no one is perfect, and mistakes do occur. If you find an error in one of our books, like a spelling mistake or faulty piece of code, we would be very grateful for your feedback. By sending in errata you may save another reader hours of frustration and at the same time you will be helping us provide even higher quality information. To find the errata page for this book, go to http://www.wrox.com and locate the title using the Search box or one of the title lists. Then, on the book details page, click the Book Errata link. On this page you can view all errata that has been submitted for this book and posted by Wrox editors. A complete book list including links to each book’s errata is also available at www.wrox.com/misc-pages/booklist.shtml. If you don’t spot “your” error on the Book Errata page, go to www.wrox.com/contact/techsupport​ .shtml and complete the form there to send us the error you have found. We’ll check the information and, if appropriate, post a message to the book’s errata page and fix the problem in subsequent editions of the book. xxviii Download at Boykma.Com Introduction p2p.wrox.com For author and peer discussion, join the P2P forums at p2p.wrox.com. The forums are a Web-based system for you to post messages relating to Wrox books and related technologies and interact with other readers and technology users. The forums offer a subscription feature to e-mail you topics of interest of your choosing when new posts are made to the forums. Wrox authors, editors, other industry experts, and your fellow readers are present on these forums. At http://p2p.wrox.com you will find a number of different forums that will help you not only as you read this book, but also as you develop your own applications. To join the forums, just follow these steps: 1. Go to p2p.wrox.com and click the Register link. 2. Read the terms of use and click Agree. 3. Complete the required information to join as well as any optional information you wish to pro- vide and click Submit. 4. You will receive an e-mail with information describing how to verify your account and com- plete the joining process. You can read messages in the forums without joining P2P but in order to post your own messages, you must join. Once you join, you can post new messages and respond to messages other users post. You can read messages at any time on the Web. If you would like to have new messages from a particular forum e-mailed to you, click the Subscribe to this Forum icon by the forum name in the forum listing. For more information about how to use the Wrox P2P, be sure to read the P2P FAQs for answers to questions about how the forum software works as well as many common questions specific to P2P and Wrox books. To read the FAQs, click the FAQ link on any P2P page. Download at Boykma.Com xxix Download at Boykma.Com 1 Introducing IIS 7.0 Microsoft Internet Information Services (IIS) version 7.0 was introduced with the Windows Vista operating system as the main Windows web server. The same web server is going to be utilized by Windows Server 2008 with the same features, which means developing with Windows Vista IIS 7.0 will cost nothing when it is time to deploy on Windows Server 2008 IIS 7.0. IIS 7.0 is a revolution in terms of web application processing and handling. It has been re-architected to provide a more robust, extensible, componentized web server that gives developers a better opportunity to integrate more into its features. This chapter starts with an overview of new IIS 7.0 features. Application pools and worker processes are reviewed before diving into more advanced topics. The discussion goes deeper to cover the major components inside IIS 7.0. IIS 7.0 introduces the concept of modules as a new architectural design. Both native and managed modules are covered, with a brief description of each. The chapter ends by giving an overview on the request processing in IIS 7.0 and the new application pool modes: Integrated and Classic. By the end of this chapter, you will have a good knowledge of the following: ❑❑ IIS 7.0 features overview ❑❑ Application pool and worker processes ❑❑ IIS 7.0 components ❑❑ Managed and native modules inside IIS 7.0 ❑❑ IIS 7.0 request processing pipeline ❑❑ Integrated and Classic mode application pools Download at Boykma.Com Chapter 1: Introducing IIS 7.0 Overview of IIS 7.0 IIS 7.0 is the new web server that ships with Windows Vista and Windows Server 2008. Similar to the previous versions of IIS, this new version will continue to handle and process web requests that arrive at the Windows machine. The most mature version of IIS before the current one is IIS 6.0 which ships with Windows Server 2003. IIS 6.0 is very robust in terms of security, speed, process management, and reliability. IIS 7.0 builds its core engine on its predecessor and improves several areas. In addition, many new features have been added, making it extensible and manageable, thus leveraging IIS 7.0 to be a web server platform powerful enough to handle the challenges of present and future web applications. The new IIS 7.0 features and characteristics are briefly summarized and presented in the next few sections to give a high-level overview of what has been done to improve the web server. Modular Architecture As mentioned above, IIS 7.0 bases its core engine on the best features of IIS 6.0 and adds to them the extensibility and accessibility for developers through its modular core engine. IIS 7.0 is based on a plugin architecture that allows developers to have a hand in the processing of web requests. IIS 7.0 provides extensibility through its runtime pipeline, configuration management, and operational features to have a customizable web server for varying needs and requirements. Making IIS 7.0 modular gives you the chance to customize it according to personal preferences and needs. Contrary to how the IIS 6.0 was configured, IIS 7.0 has most of its modules available but not installed. An administrator or developer can choose what modules or features to install and activate and what modules to deactivate. This provides both administrators and developers with a robust and reliable capability to configure the web server as needed. Figure 1-1 shows the new IIS 7.0 Manager listing the 40 available managed and native modules or features that ship with a full installation. Figure 1-1 2 Download at Boykma.Com Chapter 1: Introducing IIS 7.0 All modules are not installed by default, unless specified. Any module can be uninstalled and removed from the runtime pipeline processing, giving a flexible and dynamic experience in terms of choosing what to configure from built-in modules or even adding new modules and features. From the security point of view, an administrator or developer can choose what modules to include in the processing, hence affecting the overall performance of loading the configured modules to handle requests. This modular architecture helps reduce surface attacks by having the freedom to choose the modules to include and provides better performance by having the administrator or developer install only the required set of modules or features. IIS 7.0 managed and unmanaged modules are covered in detail later in this chapter. Web server features or modules are configured through XML configuration files. The configuration files (discussed in a later section) are built into a hierarchy where at every level modules or features are configurable. A Microsoft TechNet resource is available online that lists all the modules and features contained in IIS 7.0 and shows which modules are installed by default and which can be added later: http://technet2.microsoft.com/WindowsServer2008/en/library/ 0d35e92b-ddb7-4423-b1e5-df550e25713b1033.mspx Developing Modules and Features The modular architecture introduced above discusses the ability to customize the modules installed on the web server whether by adding new ones or uninstalling existing ones. Adding new modules is easier with the new extensibility API for developing modules to integrate into IIS. All of the native modules installed or shipped with IIS are developed on top of this extensibility API and this API is public, which means any developer can take that API and either redevelop an existing module or develop a new module as required. The new extensibility API is built with C++ and it fully represents the new web server object model. The set of classes allows the developer to develop modules that can participate in request processing on IIS. This model is a replacement of the ISAPI extensibility model and is much easier to develop with since the new model includes a type-safe and well-encapsulated object model. Every needed web server object has a corresponding specialized object interface in the new API. For example, the IHttpRequest interface allows custom modules developed on top of the new extensibility API to access all the information related to the request under processing. The IHttpResponse interface allows custom modules to interact with the response generated for a request processed by IIS 7.0. The new extensibility API even excels in terms of memory allocation and state management over ISAPI. In the days of ISAPI extensions, the developer had to take care of allocating and unallocating memory as required. The new extensibility API and most of the new IIS 7.0 APIs allocate server-managed memory for the data processed, which is different from the days of ISAPI extensions where developers had to take care of all the mess. Finally, the new extensibility API allows modules to access features that were impossible to access before, such as request buffering and other IIS request processing tasks. What about ASP.NET developers who are not ready to learn C++ to develop new modules for IIS? IIS 7.0 allows ASP.NET developers to utilize their existing ASP.NET module or create new ones using both the .NET 2.0 and 3.5 Frameworks and plug them automatically into the IIS request pipeline. In a later section, the ASP.NET integration process is explained in more depth. Download at Boykma.Com 3 Chapter 1: Introducing IIS 7.0 Deployment and Configuration Management IIS 7.0 uses a new configuration system that is conceptually much different from the IIS 6.0 centralized metabase configuration system. The new configuration system borrows many ideas from the current .NET 2.0 and 3.5 Frameworks configuration system, which is based on section groups and sections. IIS 7.0 configuration system is based on XML configuration files mainly the ApplicationHost.config and Administration.config configuration files. Both of these files get deployed on the machine when IIS 7.0 is installed The configuration file of concern for most of the tasks related to IIS 7.0 is the ApplicationHost.config configuration file that contains all the new web server meta-data. This configuration file contains global- and application-specific configuration sections. It resembles the .NET Frameworks configuration files: machine.config and the root web.config configuration files. The web server configuration file can be reached by browsing to the %WINDIR%/System32/inetsrv/config folder. Figure 1-2 shows the two main sections of the ApplicationHost.config configuration file. System.applicationHost ApplicationHost.config System.webServer Figure 1-2 applicationPools listenerAdapters Log Sites webLimits Asp Caching Cgi defaultDocument directoryBrowsing globalModules Handlers httpCompression httpErrors httpLogging httpProtocol httpRedirect httpTracing isaoiFilters Modules odbcLogging Security serverRuntime serverSideInclude staticContent Tracing urlCompression validation The two main section groups are the and the section groups. The section group contains all the global settings for the web server, including the sites, applicationPools, listenerAdapaters, and so forth. This section is locked down and cannot be extended by any application hosted insideIIS. 4 Download at Boykma.Com Chapter 1: Introducing IIS 7.0 The section defines all the configuration information on all sites hosted by the web server. At the root node there is the Default Web Site that points to the site located at %SystemDrive%\ inetpub\wwwroot. To add a new website to IIS 7.0, simply add a new application node specifying the virtual path attributes together with a virtualDirectory sub-node setting the path and physicalPath attributes. With the above configuration, a new website has been added to IIS and can be accessed by http://localhost/MyApp. The other section group, , holds all the configurable sections for an application. For instance, this section contains configuration information about all the modules installed on the web server, a configuration section for directory browsing, and all the rest of the sections shown in Figure 1-2. Note that with the new configuration system introduced by IIS 7.0, an administrator can configure the and then select which section groups and sections from the can be changed and edited by the application’s web.config configuration file. This eliminates the need for a site owner to contact the administrator to change any settings in IIS, which was always happening before the release of IIS 7.0. This makes deployment with IIS 7.0 much easier. A developer can configure the configuration section group during the development stage and then once the application is deployed, all the settings that were applied locally on IIS 7.0 would have the same effect on the hosting server given the fact that the administrator on the hosting server has already unlocked most of the configurable sections within the . For instance, a developer can override the default web server settings for the default document for an application and set it to a customized page name. The configuration section group is the only section group in the Application​ Host.config configuration file that can be extended and configured in the web.config configuration file of an application. The default documents configured on the web server are cleared out and a new customized default document for the current application is set to point to MyPage.aspx. Download at Boykma.Com 5 Chapter 1: Introducing IIS 7.0 In regard to security, administrators are allowed to select which sections of the to allow for editing and which are locked. For instance, an administrator can unlock many sections that do not pose any threat to the security of the web server as a whole and leave open all the sections that site owners usually require to change per application. When a request reaches IIS for a resource, the different configuration files are joined together in a hierarchy to form single, unified configuration settings that apply to the current request. Figure 1-3 shows the process of how the different configuration files are grouped together to form a final web.config configuration file. Machine.config web.config (root) ApplicationHost.config web.config (%SystemDrive%/inetpub/ wwwroot) web.config (%SystemDrive%/inetpub/ wwwroot/MyApp) Figure 1-3 web.config (sub applications) The machine.config file is merged with the web.config configuration file located in the root folder of the .NET 2.0 Framework, which is a shared folder used by both ASP.NET 2.0 and ASP.NET 3.5. The ApplicationHost.config configuration file is added to the result of the above grouping, and then the combined configuration settings are grouped with the web.config configuration file in the root website of the web server. The final result is added to the grouped configuration settings of the web.config configuration file of the executing application with its sub-applications’ web.config configuration files. An IIS resource is available online that gives a detailed overview of the ApplicationHost.config configuration file: http://learn.iis.net/page.aspx/124/introduction-to-applicationhostconfig/ Improved Administration The IIS 7.0 Manager has been developed from scratch to replace the previous version. The difference is evident through the new UI experience and quick availability for any section to check and configure. The IIS 7.0 Manager provides the UI interface experience for administrators and developers to configure the ApplicationHost.config configuration file without touching any physical resources. For instance, Figure 1-4 lists the available application pools in the ApplicationHost.config configuration file. The Manager is just a UI representation to whatever is stored in the ApplicationHost.config configuration file. Using the manager to configure IIS 7.0 helps to prevent imposing possible wrong XML tag placement. 6 Download at Boykma.Com Chapter 1: Introducing IIS 7.0 Figure 1-4 Application pools can be removed and edited, and new ones can be added. The result is stored in the ApplicationPool configuration section group inside the ApplicationHost.config configuration file. The IIS 7.0 Manager inherits the idea of extensibility from IIS 7.0 and provides an extensible API that can be used to extend its UI features, hence extending the UI experience with much more features as required. In addition, the Manager allows management delegation that helps in administrating remote websites. For example, administrators in hosting companies can configure IIS 7.0 with the major and most secure configurations and allow the sites’ owners to configure their sites remotely through their version of IIS 7.0 Manager. This does away with the need for special control panels for site owners to log into and configure their websites. Moreover, the IIS 7.0 team thought of providing developers with a managed API to allow them to configure the IIS 7.0 configuration settings programmatically. The new API is called the Microsoft.Web .Adminisration API. Before this API can be used in Visual Studio, a reference has to be added to the Microsoft.Web.Administration.dll found at %SystemDrive%:\Windows\System32\inetsrv. The main class in this new API is the ServerManager .NET class. This class contains properties for the sites, applications, virtual directories, application pools, and worker processes. Download at Boykma.Com 7 Chapter 1: Introducing IIS 7.0 C# using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.Web.Administration; namespace Microsoft.Web.Administration { public class Program { static void Main(string[] args) { // Get a reference to the factory object // ServerManager var manager = new ServerManager(); // Define a new website manager.Sites.Add( “ProgrammaticSite”, @”D:\ProgrammaticSite\”, 8080); // Commit changes to the ApplicationHost.config manager.CommitChanges(); } } } VB.NET Imports System Imports System.Collections.Generic Imports System.Linq Imports System.Text Imports Microsoft.Web.Administration Namespace Microsoft.Web.Administration Public Class Program Shared Sub Main(ByVal args() As String) ‘ Get a reference to the factory object ‘ ServerManager Dim manager = New ServerManager() ‘ Define a new website manager.Sites.Add(“ProgrammaticSite”, “D:\ProgrammaticSite\”,_ 8080) ‘ Commit changes to the ApplicationHost.config manager.CommitChanges() End Sub End Class End Namespace 8 Download at Boykma.Com Chapter 1: Introducing IIS 7.0 The preceding code creates a new instance of the ServerManager factory object. Then it adds a new site by accessing the Sites property and specifying the site name, physical path, and the port, and finally, a call to the CommitChanges method to reflect the changes in the ApplicationHost.config configuration file. The result of executing the preceding code can be checked in the configuration section: A new site entry is created within the configuration section group. The new site specifies the application’s physical path, virtualDirectory’s physicalPath, and the protocol binding. Moreover, IIS 7.0 provides an additional tool called appcmd.exe that allows administrators and developers to configure the web server from the command prompt to create and configure sites, applications, virtual directories, start and stop application pools, recycle application pools, and much more. The utility is very rich in options and even presents a deeper configuration interface than that of IIS 7.0 Manager. The book titled Professional IIS 7 and ASP.NET Integrated Programming (Wrox) explains in detail the IIS 7.0 Manager and the new Administration API. In addition, it includes informative chapters on the new IIS 7.0 configuration system and many more topics. An IIS resource is available online that gives a detailed overview of the Microsoft.Web.Administration API: http://learn.iis.net/page.aspx/165/ how-to-use-microsoftwebadministration/ ASP.NET Integration ASP.NET, since its release, has been used for several years to provide high level and powerful web applications developed purely within the context of the .NET Framework. A revolutionary stage has been introduced with the release of ASP.NET 2.0 that introduced new concepts and services to web development in ASP.NET. ASP.NET 3.5 continues to use the ASP.NET 2.0 at its core and adds to it additional new features and improvements to help developers build better and robust Web solutions. So far, ASP.NET has been used only as a framework for developing dynamic web applications. IIS 7.0 leverages ASP.NET 2.0 and ASP.NET 3.5 to extensibility frameworks to extend the new web server. IIS 6.0 handles requests for ASP.NET pages through ISAPI filters and extensions. Request handling is delegated to the ASP.NET ISAPI extension, the ASP.NET pipeline is triggered to handle the new request, and a response is generated and finally handed back to the IIS to deliver it to the requesting client. APS.NET has no control over what is being sent to its engine, since it is solely controlled by the IIS core engine. Only requests defined by the ASP.NET engine can be passed and processed, but what about other content? For instance, what if an ASP.NET application wants to secure access to some old Classic ASP pages using the same FormsAuthenticationModule used to protect ASP.NET resources? Before IIS 7.0, that was hard to do, if not impossible. If you are in a hurry to learn how to control and process non-ASP.NET content and resources through the ASP.NET pipeline, you can jump directly to Download at Boykma.Com 9 Chapter 1: Introducing IIS 7.0 Chapter 7 for a detailed discussion on how to integrate ASP.NET security with Classic ASP pages. Note that whatever applies to Classic ASP applies also to any other non-ASP.NET resource including .php, .jpg, .htm, and so on. In IIS 7.0, ASP.NET 2.0 and 3.5 can run in two different modes: Classic and Integrated. The Classic mode resembles the same model as that of IIS 6.0 and ASP.NET. ASP.NET 1.1 applications running inside IIS 7.0 can only be run using the Classic mode. When an ASP.NET 2.0 or 3.5 application is running in the Integrated mode, however, the ASP.NET engine gets unified with the IIS 7.0 engine, hence they share the same request pipeline. IIS’s native C++ modules and ASP.NET HttpModules work together on processing a request. A request is processed by the configured native modules and any module registered with ASP.NET. One of the clear and shining results of this unified integration is that ASP.NET can now have a say when processing any content resource (and not only ASP.NET resources), a feature not present before the days of IIS 7.0. Figure 1-5 shows the unified request pipeline in processing a request in IIS 7.0. Authentication Anonymous Basic Windows Forms HTTP Request Execute Handler Page Webservice Trace Send Response Figure 1-5 Compression Logging When it is time for IIS to authenticate a request, it executes all the configured native and managed authentication modules at the same time. The same applies for any stage inside IIS 7.0. This signifies again the power of having both ASP.NET modules and native modules execute side by side in handling a request. More on ASP.NET integration with IIS 7.0 is covered in detail in Chapter 2. 10 Download at Boykma.Com Chapter 1: Introducing IIS 7.0 Security Improvements IIS 7.0 security is based on the robustness of IIS 6.0’s security. By default, when IIS 6.0 is installed, it is installed in a locked-down mode, meaning that only handling of static files and the World Wide Web Publishing Service (WWW Service) are installed and enabled. The rest of services that operate on top of IIS 6.0 (including ASP, ASP.NET, and so forth) are disabled and can be added and enabled at any time by the administrator. IIS 7.0 takes the locked-down strategy of IIS 6.0 one step further and follows the same locked-down pattern by installing fewer services at installation time. Having fewer features installed and enabled minimizes the risk of attack on the web server and minimizes the work done by the administrator to keep updating with patches and service packs on the different services installed, whether enabled or not. By making use of the modular architecture, an administrator can easily, at any time, install a new module or feature required by applications hosted by the web server. Enabling the unified request pipeline in IIS 7 by configuring applications with the Integrated mode, the web server gains a more secure environment through the use of ASP.NET security modules. These modules include the FormsAuthenticationModule and the Membership and Role management services introduced early in ASP.NET 2.0 that still constitute a major feature in ASP.NET 3.5. Not only can ASP.NET benefit from these modules, but IIS 7.0 also gets better protection by utilizing these modules to protect the resources hosted in its environment. In addition, IIS 7.0 introduces URL Authorization, which is inspired (more or less) by the architecture of the ASP.NET URL Authorization. The new authorization system allows administrators to add declarative access control rules for the hosted applications to protect their resources. This new feature integrates well with the ASP.NET Membership and Role management services. A more detailed discussion on URL Authorization is given in Chapter 3 of this book. Moreover, the IIS 7.0 team replaced the old URL Scan security tool with a new RequestFilteringModule that gives administrators finer control on what to allow and disallow in a request targeting the web server. The RequestFilteringModule, as shown in the following code, can be configured through the configuration section group either in the ApplicationHost.config configuration file or through the application’s web.config configuration file. For instance, to configure IIS 7.0 to process ASP.NET web pages only, the RequestFilteringModule is configured to allow only ASP.NET web pages and prevent all other file extensions from being served and processed. Download at Boykma.Com 11 Chapter 1: Introducing IIS 7.0 For further details, an IIS resource is available online that gives a wider overview of the new Request​ FilteringModule: http://learn.iis.net/page.aspx/143/how-to-use-request-filtering/ Another security feature in which IIS 7.0 excels is the IIS Manager. As mentioned above, when applications are hosted locally, the site owner can configure IIS 7.0 settings by either direct access to the ApplicationHost.config configuration file, or through the appcmd.exe command-line utility, or programmatically by utilizing the Microsoft.Web.Adminsitration API. When configuring remote applications, IIS Manager provides remote connections to site owners through their local instance of the manager through firewall-friendly HTTPs connections. Based on the restrictions set by the remote administrator, a site owner connects to the remote web server through the local instance of the manager. The user gets authenticated on the remote server either by Windows authentication, if the user has a Windows account on the remote server, or by custom authentication of the ASP.NET Membership services. Once authenticated, the site owner can now configure the web server’s settings under the limitations set by the remote administrator. Not only does IIS Manager allow remote connections; it also allows administrators to configure the IIS Manager UI to select the features to show for remote connections. This is yet another security protection on the hosting web server. Finally, IIS 7.0 introduces a new IIS anonymous account, the IIS_USR. This built-in account has no expiration date, nor does it need any password synchronization among different machines. Also, a new group is IIS_IUSRS that replaces the old IIS_WPG group. This group injects itself into the identity of the Worker process automatically at runtime. This makes the process of specifying another custom account for the Worker process identity easier without having to worry about adding this custom account to the IIS_IUSRS group. Since the IIS_IUSR and IIS_IUSRS are built-in, any Windows access control lists (ACLs) that an administrator or developer assigns on one machine can be copied to another machine, for instance, from the development machine to the testing and deployment machine, without any further worries, making the deployment process easier and more flexible. Troubleshooting Improvements IIS modular architecture not only introduces flexibility and robustness in configuring the web server, but it also adds more complexities when it comes to debugging or tracing requests when a problem occurs while a resource is being executed by the web server. Therefore, several new troubleshooting improvements have been added to allow administrators and developers to better detect what is going wrong with their applications. A new, improved tracing system is added to the IIS infrastructure that is capable of capturing all related information for a request being processed by the web server. This way, an administrator can refer back at any time to check the status of requests being served by IIS. The trace information generated by the web server can be monitored and listened to by a new feature, the Failed Request Tracing feature. This new feature is basically configured to listen only to failure requests and logs them to the hard-disk. Before using this feature, it must be enabled in the IIS Manager tool. Figure 1-6 shows how to open up the Failure Request Tracing form to enable/disable the feature and to specify the path where to log the trace data. By default, the Failure Request Tracing feature passes all successful requests and logs only the failed ones, as mentioned above. In addition, an administrator can define Failure Request Tracing Rules to specify what trace information to listen to in the web server tracing system. To define these rules, the Failing Request Tracing Rules feature can be configured inside the IIS Manager tool reached by selecting Server Name ➪ ​Web Sites ➪ ​Default Web Site ➪ ​Failed Request Tracing Rules under the IIS section. 12 Download at Boykma.Com Chapter 1: Introducing IIS 7.0 Figure 1-6 In addition, IIS provides new error information pages when errors are detected in the resources being processed. These error pages are similar in concept to the error pages generated by ASP.NET when an exception or error occurs in the application while a request is being made to any of its resources. The IIS error information pages give details about the problem that occurred, what module caused the problem, if any, where to find more tracing information about the specific failure of the request, and even more information that helps the administrator or developer to locate the problem quickly. The detailed error pages are configured for local access only by default and can be localized for any culture of preference. To better benefit from the unified integration model between IIS and ASP.NET, the new web server’s tracing system exposes its functionality to the modules created by the managed code in ASP.NET. The new tracing system is extensible enough to allow the managed modules registered in IIS to make use of the tracing information and to emit tracing data to the IIS tracing system. ASP.NET 2.0 and ASP.NET 3.5 contain the System.Diagnostics.TraceSource class that makes the developer’s life easier in handling tracing events, data, and information (shown in the following code). The tracing system present in IIS 7.0 integrates with the tracing system in ASP.NET 2.0 and 3.5, thus allowing tracing information generated by ASP.NET to flow to the IIS 7.0 tracing system. C# using System; using System.Diagnostics; using System.Web; public class CustomTracing : IHttpModule { // Private member to hold a reference to the // TraceSource class private TraceSource tsTracing; Download at Boykma.Com 13 Chapter 1: Introducing IIS 7.0 /// /// Initialize event in the HttpModule /// /// public void Init(HttpApplication application) { // Attach to the EndRequest event application.EndRequest += new EventHandler(application_EndRequest); // Define the trace source tsTracing = new TraceSource(“tsTracing”); } /// /// Handles the end request event /// /// /// void application_EndRequest(object sender, EventArgs e) { // Write a message to the configured trace listeners mentioning the start of // a logical operation or event, which is in this case beginning of the // EndRequest method. this.tsTracing.TraceEvent( TraceEventType.Start, 0, “[CustomTracing MODULE] START EndRequest”); // Get a reference to the HttpContext var app = (HttpApplication)sender; var context = app.Context; // Write some text to the response stream context.Response.Write( “Testing Tracing from ASP.NET and integrating into IIS 7.0”); this.tsTracing.TraceEvent( TraceEventType.Verbose, 0, “A debugging trace message to the trace listener!”); this.tsTracing.TraceEvent( TraceEventType.Critical, 0, “A fatal error or crash message to the trace listener!”); this.tsTracing.TraceEvent( TraceEventType.Error, 0, “A recoverable error message to the trace listener!”); this.tsTracing.TraceEvent( TraceEventType.Information, 0, “An informational message to the trace listener!”); // Write a message to the configured trace listeners mentioning the end of a // logical operation or event, which is in this case end of the EndRequest // method 14 Download at Boykma.Com Chapter 1: Introducing IIS 7.0 this.tsTracing.TraceEvent( TraceEventType.Stop, 0, “[CustomTracing MODULE] STOP EndRequest”); } #region IHttpModule Members public void Dispose() { throw new NotImplementedException(); } #endregion } VB.NET Imports System Imports System.Diagnostics Imports System.Web Namespace CustomTracingModule Public Class CustomTracing Implements IHttpModule ‘ Private member to hold a reference to the ‘ TraceSource class Private tsTracing As TraceSource ‘’’ ‘’’ Initialize event in the HttpModule ‘’’ ‘’’ Public Sub Init(ByVal application As HttpApplication) Implements_ IHttpModule.Init ‘ Attach to the EndRequest event AddHandler application.EndRequest, AddressOf application_EndRequest ‘ Define the trace source tsTracing = New TraceSource(“tsTracing”) End Sub ‘’’ ‘’’ Handles the end request event ‘’’ ‘’’ ‘’’ Private Sub application_EndRequest(ByVal sender As Object,_ ByVal e As EventArgs) ‘ Write a message to the configured trace listeners ‘ mentioning the start of a logical operation ‘ or event, which is in this case beginning of the EndRequest method. Me.tsTracing.TraceEvent(TraceEventType.Start,_ 0,_ “[CustomTracing MODULE] START EndRequest”) Download at Boykma.Com 15 Chapter 1: Introducing IIS 7.0 ‘ Get a reference to the HttpContext Dim app = CType(sender, HttpApplication) Dim context = app.Context ‘ Write some text to the response stream context.Response.Write(“Testing Tracing from ASP.NET and integrating into IIS 7.0”) Me.tsTracing.TraceEvent(TraceEventType.Verbose,_ 0,_ “A debugging trace message to the trace listener!”) Me.tsTracing.TraceEvent(TraceEventType.Critical,_ 0,_ “A fatal error or crash message to the trace listener!”) Me.tsTracing.TraceEvent(TraceEventType.Error,_ 0,_ “A recoverable error message to the trace listener!”) Me.tsTracing.TraceEvent(TraceEventType.Information,_ 0,_ “An informational message to the trace listener!”) ‘ Write a message to the configured trace listeners ‘ mentioning the end of a logical operation ‘ or event, which is in this case end of the EndRequest method Me.tsTracing.TraceEvent(TraceEventType.Stop,_ 0, “[CustomTracing MODULE] STOP EndRequest”) End Sub #Region “IHttpModule Members” Public Sub Dispose() Implements IHttpModule.Dispose Throw New NotImplementedException() End Sub #End Region End Class End Namespace The preceding code defines a local instance of the TraceSource class to hold all the tracing information by the managed ASP.NET module. The name of the TraceSource is important, as it will be referenced later as a source for the IIS trace listener. The HttpModule subscribes to the EndRequest event of the module and writes some dummy text into the response stream. Several trace messages have been written to the ASP.NET tracing system using the TraceSource object. Several methods are available in the aforementioned object, one of which is the TraceEvent method that takes as one of the inputs a value from the TraceEventType enumeration that defines the purpose of the trace message and another input, the trace message to be sent to the trace listener. There are several values in the TraceEventType enumeration that defines the different contexts in which a trace message might be present. .NET 3.5 Framework ships with the System.Web.IisTraceListner class, which is used to route tracing information from ASP.NET tracing system to the IIS tracing infrastructure. To define the trace listener and attach it as a listener to the TraceSource, the configuration section in the web.config configuration file is used. 16 Download at Boykma.Com Chapter 1: Introducing IIS 7.0 The preceding configuration section defines the new IIS trace listener with a switch to capture all tracing information. In addition, the tracing source, which is in this case the TraceSource instance defined previously in the custom tracing managed module, is added and configured with the IISTraceListener. The preceding configuration section makes sure all the tracing information from ASP.NET is routed correctly to the IIS tracing system. The Failed Request Tracing feature can then be used, either through the default behavior to capture only failure trace information for failing requests or by adding custom rules to capture specific tracing information descending from the ASP.NET tracing system. Finally, native developers can now troubleshoot the state of the IIS web server through the new Runtime Status and Control (RSCA) API known as “reeska.” This new API allows native developers, mainly C++ developers, to examine the real-time status of the server by checking the active states of the sites and application pools, the running worker processes, and even to check current requests that are being processed. Developers can check the normal flow of page execution on the server and identify bottlenecks, while the different modules take their part in the request processing in the IIS pipeline. In addition, RSCA provides a means to control the state of the web server by stopping and starting the service, recycling application pools, starting and stopping sites, etc. These features are similar to the appcmd.exe command-line tool mentioned previously in this chapter. An IIS resource is available online that gives an overview on developing managed tracing modules and routing the ASP.NET trace information to the IIS 7.0 tracing system: http://learn.iis.net/page .aspx/171/how-to-add-tracing-to-iis-7-managed-modules/ Application Pools IIS 6.0 introduced the concept of application pools when operating in the worker process isolation-mode compared to working in the IIS 5 mode. An application pool by definition is a unit of separation, at the web server level, that is used to logically group applications into different boundaries, hence providing an isolation of execution from one application to another. If an application in one of the application pools on the web server crashes, not all the applications on the web server will be crashed too. This is because if each application is assigned to a separate application pool, then only this specific application Download at Boykma.Com 17 Chapter 1: Introducing IIS 7.0 pool will recycle and all applications assigned to the same application pool will also crash. Other applications assigned to other application pools continue to function properly as if nothing happened on the web server. Therefore, application pools provide isolation of execution under the boundaries of the server resources allocated to every application pool, which are allocated differently from one application pool to another. In the previous release of IIS, the web server was configured to either run in the worker process isolation mode or in the IIS 5.0 mode. However, in IIS 7.0, an application pool is created and its managed pipeline mode property is either set to Integrated mode or Classic mode. This means that the managed pipeline mode is not configured on the web server as a whole. On the contrary, several application pools can be created on IIS 7.0 with different managed pipeline modes, and applications can be assigned to any of those application pools, hence it is possible to run applications on the same web server with different modes of execution. Figure 1-7 shows the basic settings window for any application created inside IIS 7.0. Figure 1-7 By opening the IIS manager tool, on the Actions tab on the right of the manager, there is a link to view application pools. All of the application pools created on the web server are listed. Right-clicking any of the application pools and selecting basic settings yields the screen shown in Figure 1-7. There is nothing special about it, but the managed pipeline mode combo box that allows you to choose either the Integrated or Classic mode. Integrated Mode When an ASP.NET 2.0 or 3.5 application is assigned to an application pool running in the Integrated mode, the application will benefit from the IIS and ASP.NET unified request processing pipeline. This means the request is processed by both the native and managed installed modules and ASP.NET will have the ability to process all types of content within that specific application. This mode is recommended when there is a need to execute an application in the Integrated mode, and it is the preferred mode to configure the application pools. Several additional and advanced settings can be set by right-clicking on the specific application pool and selecting Advanced Settings. Classic Mode The Classic mode resembles an IIS 6.0 application pool when the web server is running in a worker process isolation-mode. In IIS 7.0, applications are still given the opportunity to function as if they are being served by IIS 6.0. When an application is assigned to an application pool configured to run in the Classic mode, IIS 7.0 handles the execution of the application in the same way as IIS 6.0. For instance, if 18 Download at Boykma.Com Chapter 1: Introducing IIS 7.0 an ASP.NET application is assigned to function under an application pool configured with Classic mode, the default and only available option for ASP.NET 1.1 application, when a request reaches IIS for that application, only the native modules will be executed on the request, then IIS 7.0 hands the request to the aspnet_isapi.dll extension to be processed by the ASP.NET runtime. Hence, IIS is able to process the request with all the installed native modules and ASP.NET will have another round in executing its managed modules; the same old-fashioned way of executing applications under IIS 6.0 when configured to run in the worker process isolation mode. If any ASP.NET application for some reason cannot run inside the application pool Integrated mode, it is recommended to keep it configured with the Classic mode under IIS 7.0. It will be executed and processed as if it is hosted in an IIS 6.0 environment. IIS 7.0 Components IIS 7.0 is made up of several components that form the web server internal core engine. These components include protocol listeners, services such as the w3svc service and the WAS service, protocol adapters, and many more core components. This section will present an overview of some of the protocols and services that handle request processing inside IIS 7.0. Protocol Listeners Protocol listeners are services in which each service is configured to listen and process a specific protocol request coming from the network on which the machine hosting the web server resides. For instance, one of the listeners installed on a Windows machine keeps on waiting and listening for any web request arriving on the machine. There are additional listeners also present to listen to other, different protocols. When a request is received by a listener, it forwards it to IIS 7.0 to be processed. Once a request is processed by IIS 7.0, the response generated is sent back to the protocol listener that originally sent the request. Finally, the response is handed back to the requestor. An example of a protocol listener is the HTTP listener called Hyper Text Protocol Stack. This is the main protocol listener for all HTTP requests arriving on a Windows machine. When an HTTP request is first received by Windows Vista or Windows Server 2008, the initial handling is actually performed by the kernel-mode HTTP driver: http.sys. World Wide Web Publishing Service In IIS 6.0 the WWW service was responsible for several tasks at once. These tasks included HTTP administration and configuration, process management, and performance monitoring. In IIS 7.0, this has changed and the WWW Service now acts as a listener adapter for http.sys. A listener adapter is responsible for configuring the http.sys protocol listener with the IIS 7.0 configuration information stored in the ApplicationHost.config configuration file. It then waits for changes in the configuration information to reflect them into the http.sys, and finally notifies the Windows Process Activation Service (WAS) when a new HTTP request enters the local queue. WWW Service functionality has been split into other services. It has preserved its role as a listener adapter, however, the rest of its responsibilities have been passed into another service called the Windows Process Activation Service. Download at Boykma.Com 19 Chapter 1: Introducing IIS 7.0 Windows Process Activation Service In IIS 7.0, the WAS is the second half of the WWW service that was present in the IIS 6.0 days. The WAS is a new service that has three main parts. Figure 1-8 shows the architecture and main components of the WAS. Windows Process Activation Service Process Manager Configuration Manager Listener Adapter Interface Figure 1-8 The configuration manager is responsible for reading the configuration information from the ApplicationHost.config configuration file. This manager reads global configuration information and protocol configuration information for both HTTP and non-HTTP protocols in order to be able to configure all protocol listeners installed on the web sever machine. It also reads application pool configuration information to know what application pools are present when processing requests on the server. It reads site configuration information, including the different applications included in each site together with the bindings defined on each application, and finally, reads the application pool each application belongs to. Such information helps the WAS when processing a request to know which site and application the request belongs to so that it gets handled by the right application pool. In addition, the configuration manager gets a notification when the ApplicationHost.config configuration file changes so that it updates its data with the new ones and reflects this on the available protocol listeners. The process manager is responsible for managing the application pools and worker processes for both HTTP and non-HTTP requests. It manages the state of the application pool by stopping, starting, and recycling it. In addition, when WAS receives a new request from one of the configured protocol listeners, it determines to which application the request belongs. It then checks with the configuration manager for the application pool of the application that the current request belongs to. Once the application pool is determined, it checks to see if there is any worker process currently active. If it finds one, it sends the request to the application pool to be processed by the worker process. If there is no worker process active inside the application pool, WAS instantiates a new one to process the current and upcoming requests. The last component of the WAS is the unmanaged listener adapter interface. This layer inside the WAS defines how the external listeners communicate the requests they receive into the WAS in order to process them by the web server. 20 Download at Boykma.Com Chapter 1: Introducing IIS 7.0 On startup of IIS 7.0, WAS gets initiated and performs several tasks. Figure 1-9 shows the flow of interaction when WAS first configures the protocol listener adapters. ApplicationHost.config 1 Windows Process Activation Service Process Manager Configuration Manager Listener Adapter Interface 2 WWW Service 2 NetTcpActivator 3 3 HTTP.sys Figure 1-9 NetTcpActivator When WAS is instantiated, it first reads the configuration data from the ApplicationHost.config configuration file. Once the configuration information is read, it interacts with the configured protocol listener adapters to pass to them the needed configuration information. Protocol listener adapters function as the glue between the WAS and the protocol listeners. For instance, the WAS passes the configuration information into the WWW Service, the http.sys protocol listener adapter, which in turn configures http.sys to start listening for HTTP requests. Once a new request comes in, the specific protocol listener communicates the request to the WAS through the listener adapter interface, so that the request gets processed. Once a response is ready for the request, WAS passes the response back to the protocol listener responsible for delivering the response back to the client. Again, WAS uses the listener adapter interface for the incoming and outgoing communication with the protocol listeners. As shown in Figure 1-9, NetTcpActivator is the protocol listener and adapter for handling WCF requests. This indicates that WAS can process HTTP and non-HTTP requests; that means WAS can function properly without the need for the WWW Service by serving only non-HTTP requests. A good MSDN resource on the WCF listener adapters and hosting WCF applications inside IIS 7.0 is available online at http://msdn2.microsoft.com/en-us/library/ms730158.aspx Download at Boykma.Com 21 Chapter 1: Introducing IIS 7.0 IIS 7.0 Modules The modular architecture of IIS 7.0 has been discussed thoroughly at the beginning of this chapter. It is the new architecture that characterizes the web server core engine. Modules or features can be thought of as classes or objects embedding certain functionality that get executed whenever a new request is being processed by the IIS pipeline. Every installed module gets its turn in processing every request entering the IIS 7.0 pipeline. This modular architecture has several goals, but above all it protects the web server from security attacks. When a small number of modules are installed on the web server, this means there is a lower probability for a security attack on the server, hence lowering the surface attack to hackers. In addition, when a small number of modules are installed, this means less security patches and updates are required for the administrator to maintain. Moreover, being able to customize the web server to this extent gives the administrator the chance of deciding on the role of the web server by installing and uninstalling modules in the way best suited for the role intended for the web server. IIS 7.0 ships with a set of unmanaged or native modules that are all installed in case of a full installation of the web server. In addition, IIS 7.0 allows you to extend its functionality with managed modules. Each of these modules is discussed in detail. Unmanaged Modules The native modules are grouped by functionality. There are HTTP-related modules that perform tasks specific to HTTP; another set of modules perform tasks related to security; and anther set of modules perform tasks related to content (static files, directory browsing, and so on). There are a set of modules responsible for compression, modules concerned with caching, modules responsible for logging and diagnostics, and modules that help in integrating managed modules. All of these modules are fired and executed during the request-processing pipeline. The available native modules at the time of this writing together with a description are listed in the following table. Module Name HTTP Modules CustomErrorModule HttpRedirectionModule OptionsVerbModule ProtocolSupportModule RequestForwarderModule Description Sends default and configured HTTP error messages when an error status code is set on a response. Supports configurable redirection for HTTP requests. Provides information about allowed verbs in response to OPTIONS verb requests. Performs protocol-related actions, such as setting response headers and redirecting headers based on configuration. Forwards requests to external HTTP servers and captures responses. 22 Download at Boykma.Com Chapter 1: Introducing IIS 7.0 Module Name TraceVerbModule Security Modules AnonymousAuthModule BasicAuthModule CertificateMappingAuthenticationModule DigestAuthModule IISCertificateMappingAuthenticationModule RequestFilteringModule UrlAuthorizationModule WindowsAuthModule Content Modules CgiModule DavFSModule DefaultDocumentModule DirectoryListingModule IsapiModule IsapiFilterModule ServerSideIncludeModule StaticFileModule FastCgiModule Description Returns request headers in response to TRACE verb requests Performs Anonymous authentication when no other authentication method succeeds. Performs Basic authentication. Performs Certificate Mapping authentication using Active Directory. Performs Digest authentication. Performs Certificate Mapping authentication using IIS certificate configuration. Performs URLScan tasks, such as configuring allowed verbs and file extensions, setting limits, and scanning for bad character sequences. Performs URL authorization. Performs NTLM integrated authentication. Executes CGI processes to build response output. Sets the handler for Distributed Authoring and Versioning (DAV) requests to the DAV handler. Attempts to return the default document for requests made to the parent directory. Lists the contents of a directory. Hosts ISAPI extension DLLs. Supports ISAPI filter DLLs. Processes server-side includes code. Serves static files. Supports FastCGI, which provides a highperformance alternative to CGI. Continued Download at Boykma.Com 23 Chapter 1: Introducing IIS 7.0 Module Name Compression Modules DynamicCompressionModule StaticCompressionModule Caching Modules FileCacheModule HTTPCacheModule SiteCacheModule TokenCacheModule UriCacheModule Logging and Diagnostics Modules CustomLoggingModule FailedRequestsTracingModule HttpLoggingModule RequestMonitorModule TracingModule Managed Support Modules ManagedEngine ConfigurationValidationModule Description Compresses responses, and applies Gzip compression transfer coding to responses. Performs precompression of static content. Provides user-mode caching for files and file handles. Provides kernel-mode and user-mode caching in http.sys. Provides user-mode caching of site information. Provides user-mode caching of user name and token pairs for modules that produce Windows user principals. Provides user mode caching of URL information. Loads custom logging modules. Supports the Failed Request Tracing feature. Passes information and processing status to http.sys for logging. Tracks requests currently executing in worker processes, and reports information with Runtime Status and Control Application (RSCA) Programming Interface. Reports events to Microsoft Event Tracing for Windows (ETW). Provides integration of managed code modules in the IIS request-processing pipeline. Validates configuration issues, such as when an application is running in Integrated mode but has handlers or modules declared in the system.web section. 24 Download at Boykma.Com Chapter 1: Introducing IIS 7.0 The preceding modules are all installed with a full installation of IIS 7.0. However, if IIS 7.0 is installed with the default configuration and modules, a subset of those modules are installed. The modules installed by default are listed as follows. ❑❑ HTTP modules ❑❑ CustomErrorModule ❑❑ ProtoclSupportModule ❑❑ Security modules ❑❑ RequestFilteringModule ❑❑ AnonymousAuthenticationModule ❑❑ Content modules ❑❑ DefaultDocumentModule ❑❑ DirectoryListingModule ❑❑ StaticFileModule ❑❑ Content modules ❑❑ StaticCompressionModule ❑❑ Logging and diagnostics modules ❑❑ HTTPLoggingModule ❑❑ RequestMonitorModule ❑❑ Caching modules ❑❑ HttpCacheModule Managed Modules IIS 7.0 infrastructure allows the installation of .NET managed modules to participate in the requestprocessing pipeline. Allowing managed modules to function properly depends mostly on the Managed​ EngineModule mentioned above. Managed modules are ASP.NET 2.0 and 3.5 HttpModules that a .NET developer has always been used to writing, however with IIS 7.0, these modules will get the chance to work upon requests during the request-processing pipeline managed by the web server itself. The existing managed modules that can be configured with IIS 7.0 are listed in the following table. Module Name AnonymousIdentification DefaultAuthentication FileAuthorization Description Manages anonymous identifiers, which are used by features that support anonymous identification such as ASP.NET profile engine. Ensures that an authentication object is present in the context. Verifies that a user has permission to access the requested file. Continued Download at Boykma.Com 25 Chapter 1: Introducing IIS 7.0 Module Name Description FormsAuthentication Supports authentication by using Forms authentication. OutputCache Supports output caching Profile Manages user profiles by using ASP.NET profile, which stores and retrieves user settings in a data source such as a database. RoleManager Manages a RolePrincipal instance for the current user. Session Supports maintaining session state, which enables storage of data specific to a single client within an application on the server. UrlAuthorization Determines whether the current user is permitted access to the requested URL, based on the user name or the list of roles that a user is member of. UrlMappingsModule Supports mapping a real URL to a more user-friendly URL. WindowsAuthentication Sets the identity of the user for an ASP.NET application when Windows authentication is enabled. This managed modules’ information has been gathered from the official ASP.NET 2.0/3.5 documentation on MSDN. Summary In this chapter you were introduced to the new web server engine by Microsoft, IIS 7.0. IIS 7.0 ships with a new architecture that is more modular and allows administrators and developers to configure it the way they want. The main point to keep in mind about the new web server is its modular architecture. IIS 7.0 is installed with minimal modules or features. Additional modules can be installed whenever they are needed. In addition, IIS 7.0 allows developing both native and managed modules using C++ and .NET, respectively. A lot of improvements have been introduced to IIS 7.0, including security, administration and configuration, and troubleshooting improvements. New APIs are now ready for use by native and managed developers to extend the functionality of the web server. IIS 7.0 now integrates well with ASP.NET infrastructure for request processing; hence, applications now can run either in the Integrated mode or in the Classic mode application pool. ❑❑ Integrated mode: When running under the Integrated mode, the ASP.NET 2.0 or 3.5 application can take benefit from the integration between IIS 7.0 and ASP.NET so that a single unified pipeline is present where both IIS native modules and configured ASP.NET modules have a say while processing a specific request. ❑❑ Classic mode: With the Classic mode, an application will have the same environment as it had once under IIS 6.0, where the IIS 7.0 request-processing pipeline happens separately from the ASP.NET request-processing pipeline. 26 Download at Boykma.Com Chapter 1: Introducing IIS 7.0 In addition, IIS 7.0 components have been enhanced and a new major component that has been added is the Windows Process Activation Service (WAS). This service is the brain of the web server that interacts with the web server configuration system and configures protocol listener adapters that in turn configure their corresponding protocol listeners. This new service handles both HTTP and non-HTTP requests, and this gives IIS a broader field to handle so many requests from different sources. Also, this service is responsible for the process management including application pool states, stopping, starting, recycling them, and creating new worker process instances. The next chapter continues this discussion with a look at the new IIS 7.0 and ASP.NET Integrated mode. The discussion includes a thorough examination of the Integrated mode architecture as well as developing new modules and handlers in ASP.NET and integrating them with IIS 7.0 infrastructure. In addition, a study on handling migration errors is given to help in migrating an existing ASP.NET application to run under the IIS 7.0 and ASP.NET Integrated mode. Download at Boykma.Com 27 Download at Boykma.Com 2 IIS 7.0 and ASP.NET Integrated Mode Internet Information Services (IIS) version 7.0 introduces two modes of processing: Classic and Integrated mode. The Classic mode resembles the same mode of execution as that of IIS 6.0. What is new is the Integrated mode, which unifies the request-processing pipeline between the IIS infrastructure and the ASP.NET runtime. One of the striking advantages of this new mode of execution is that ASP.NET runtime is now capable of processing all types of content files including ASP.NET, HTML, ASP, PHP, and many other web resources. This is by far an improvement that makes all ASP.NET services including forms authentication, membership, role management, and many others available to different kinds of content file types in an application. This chapter starts by introducing the advantages of using the IIS 7.0 and ASP.NET Integrated mode. The discussion expands into exploring the internals and architecture of the new Integrated mode of execution. In addition, the chapter highlights the migration problems that a developer or administrator faces when upgrading an application to run inside IIS 7.0 under the Integrated mode. The chapter ends with a section on extending the IIS 7.0 infrastructure by developing managed HttpHandlers and HttpModules and installing these features from inside the application’s web.config configuration file without the need to have access to the IIS 7.0 Manager tool. When you finish reading this chapter, you will be armed with a good knowledge in: ❑❑ Advantages of using IIS 7.0 and ASP.NET Integrated mode. ❑❑ IIS 7.0 and ASP.NET Integrated mode architecture. ❑❑ Handling migration errors for applications to run under IIS 7.0 Integrated mode. ❑❑ Extending IIS 7.0 with managed HttpModules and HttpHandlers. Download at Boykma.Com Chapter 2: IIS 7.0 and ASP.NET Integrated Mode Advantages of IIS 7.0 and ASP.NET Integrated Mode IIS 7.0 introduces a new era of web development with a solid integration with ASP.NET 3.5. Web development witnessed a huge change and improvement with the release of ASP.NET 1.x, and with ASP.NET 2.0. In ASP.NET 3.5 many services were added. There is more control over the ASP.NET request-processing pipeline, new APIs were developed to make development tasks easier, in addition to many other improvements. IIS 7.0 goes far beyond and leverages ASP.NET 3.5 from being just a technology or framework to develop dynamic web applications to a framework to extend its core engine. IIS 7.0 gives the developer the choice to either continue working with the Classic mode, i.e., the IIS 6.0 mode, or enhance the development and move to the Integrated mode. Integrated mode means the integration between IIS 7.0 and ASP.NET working together, joining their efforts for a better web development experience. The benefits of such an Integrated mode can be summarized as follows: ❑❑ ASP.NET 3.5 services can now be used for all content types: In previous releases of IIS, ASP.NET did not have a say when it comes to content file types that are not registered with ASP.NET runtime. For example, when processing an ASP classic page, ASP.NET runtime could not perform any processing on that specific file type. This is due to the fact that IIS used to map several file types, including .aspx, .ascx, etc., to the ASP.NET ISAPI extension, while other file types, including .ASP, and so on, were mapped to another ISAPI extension different from that of the ASP.NET. However, with the new Integrated mode offered by IIS 7.0, ASP.NET can operate on any file type regardless of its extension and this is because when an ASP.NET application is executing under the new IIS 7.0 Integrated mode, it gets the chance to process any request, that IIS 7.0 accepts, regardless of its type. As an example, ASP.NET FormsAuthenticationModule can now be used to authenticate non-ASP.NET pages similar to the way used to authenticate ASP. NET resources. In addition, all the ASP.NET 3.5 services, including Membership, Role, and Profile management services, can be used not only with ASP.NET resources, but also with any other resource. ❑❑ Extend IIS 7.0 with ASP.NET: Previously, to extend IIS, developers had to develop native modules using both the ISAPI API and C++. Such a task was not easy at all and this forced developers who are developing in .NET, the managed code, to learn other languages like C++ to be able to develop and extend the web server core engine. In the Integrated mode, ASP.NET developers can extend the web server core engine by developing ASP.NET HttpModules. Once a module is developed, it can be registered inside IIS 7.0 modules so that it can operate during the IIS request-processing pipeline. Later in the chapter, a section is dedicated to developing a new ASP.NET module and registering it with IIS 7.0. ❑❑ Unified processing pipeline: IIS 7.0 integrates its own request-processing pipeline with ASP.NET. For instance, you can disable all native authentication modules and enable the FormsAuthenticationModule through IIS. When the authentication event fires, IIS 7.0 runs all configured authentication modules registered in an application. When it detects that forms authentication is enabled, it hands off the request to the ASP.NET module to process the request. What happens, in fact, is that the IIS 7.0 engine uses the native ManagedEngineModule to instantiate a new AppDomain instance. Inside the new AppDomain, the Common Language Runtime (CLR) is first instantiated, and after that the needed module is loaded, hence giving it the chance to operate on the request instead of a native module inside IIS. It can be seen how the native and managed modules can be used interchangeably as though they are both of the same type of modules, which they are not, but the Integrated mode gives that impression. 30 Download at Boykma.Com Chapter 2: IIS 7.0 and ASP.NET Integrated Mode This was a brief summary on the advantages of the new Integrated mode. In the coming sections, the IIS 7.0 Integrated mode architecture and an example of developing an ASP.NET module and integrating it with IIS is shown in detail. IIS 7.0 Integrated Mode Architecture The initial processing of an HTTP request on IIS 7.0 running in the Classic mode, resembling the same IIS 6.0 functionality, occurs within both IIS and a supporting protocol driver. As a result, depending on the configuration for IIS, a request may never make it far enough to be processed by ASP.NET. Figure 2-1 shows the salient portions of IIS 7.0 running in the Classic mode and Windows Server 2008 or Windows Vista/2003 that participate in request processing. Worker process W3wp.exe Static content Aspnet_isapi.dll Asp.dll Aspenet_filter.dll ISAPI filters Request for default.aspx Figure 2-1 Http.sys A request must first pass the restrictions enforced by the kernel mode HTTP driver: http.sys. The request is handed off to a worker process, where it then flows through a combination of the internal request processing provided by IIS and several ISAPI filters and extensions. Ultimately, the request is routed to the appropriate content handler, which for ASP.NET pages is the ASP.NET runtime’s ISAPI extension. The ASP.NET ISAPI extension contains the ASP.NET runtime request-processing pipeline. It can be easily concluded how the request first passes through the IIS request-processing pipeline for authentication, authorization, and all other modules. When it is time to determine the handler for the request based on the request’s file extension, in case of an ASP.NET page, IIS passes the request to the ASP.NET ISAPI extension. Once the extension is activated, the ASP.NET request-processing pipeline is fired. Figure 2-2 shows a basic sketch of what goes on inside the ASP.NET request-processing pipeline. Download at Boykma.Com 31 Chapter 2: IIS 7.0 and ASP.NET Integrated Mode ASP.NET ISAPI Extension Begin Request Authentication Handler Forms Windows ASPX ASMX EndRequest Figure 2-2 Once the request enters the ASP.NET request-processing pipeline, the different registered events start to execute. The first event is the BeginRequest event. Following is a set of events each linked to execute a specific feature until the authentication stage is reached. Based on which authentication type the application is configured with, the corresponding module executes: FormsAuthenticationModule or WindowsAuthenticationModule. Later on, the stage is reached where a handler should be selected to handle the execution of the current request according to the file extension of the request resource. Finally, the EndRequest event is fired and the response of executing the request is handed off to the IIS request-processing pipeline so that a response is generated for the client that issued the request. In IIS 7.0, the story is different. The unified request-processing pipeline that was explained earlier takes control over the execution inside IIS 7.0 when running in the Integrated mode. Unified pipeline means that both the IIS 7.0 and ASP.NET request-processing pipeline unite and execute as though they were the same pipeline of execution. This means that ASP.NET is given the privilege to have access to any IIS 7.0 intrinsic object and being able to have a hand at every stage of execution. For instance, an ASP.NET authentication module can be used to substitute any authentication native module used by IIS 7.0. With the Integrated mode enabled, ASP.NET modules become first-class citizens. They can operate on the request before any IIS module operates on it, which means ASP.NET can, for example, change the request headers before any other native module gets access to it. In addition, ASP.NET modules can operate on requests even after IIS modules finish processing the request and even sometimes, ASP.NET modules can replace existing IIS modules. Figure 2-3 shows the unified request-processing pipeline inside IIS 7.0 when operating in the Integrated mode. 32 Download at Boykma.Com Chapter 2: IIS 7.0 and ASP.NET Integrated Mode Authentication Anonymous Basic Windows Forms Execute Handler Page Webservice Trace HTTP Request Send Response Figure 2-3 Compression Logging When a request reaches IIS 7.0 to be processed, the different stages inside the request-processing pipeline start to execute. For instance, once the AuthenticateRequest event fires, IIS 7.0 checks to see what authentication modules are configured inside the configuration section group and accordingly it executes the right module(s) in the order specified in the configuration section. At this stage, an ASP.NET module that attaches to the authentication event and provides the logic to authenticate users can be added in the application’s web.config configuration file, thus removing all other native modules registered by IIS for the authentication stage. There is one exception in that, at minimum, the native AnonymousAuthenticationModule should be enabled when no other native authentication module is enabled. This unified Integrated mode allows ASP.NET modules to execute as though they were part of the IIS 7.0 infrastructure. The question that arises now is how an application interacts with the IIS engine to decide what module to run; is it the native one or the managed one? Going back to Chapter 1 when the ApplicationHost.config configuration file was introduced, it was clear that IIS infrastructure and every ASP.NET application can share the same configuration section group. The ApplicationHost.config configuration file fills the aforementioned section with global configurations and leaves it to each specific application to decide whether to use the defaults Download at Boykma.Com 33 Chapter 2: IIS 7.0 and ASP.NET Integrated Mode set globally by the web server or to update the section with specific information targeting the specific application. An important discussion to have at this stage is how IIS installs and registers the native and managed modules and who decides on which module to use. system.webServer Configuration Section Group The configuration section group located inside the ApplicationHost.config configuration file contains dedicated sections to list the native and managed modules installed on the web server. The native modules usually require not only installation but also registration, while the managed modules need only to be registered. The first section inside the that is discussed is the configuration section. The globalModules Configuration Section The configuration section installs all the native modules listed. In the Application​ Host.config configuration file the configuration section usually contains the following native modules. 34 Download at Boykma.Com Chapter 2: IIS 7.0 and ASP.NET Integrated Mode Depending on which native modules have been installed on the web server, they will be shown in the configuration section. Before being able to use any native module it should be installed and this is exactly what the configuration section does. It installs every module listed inside so that it can be used later by any IIS feature. Every module is listed by specifying the (friendly) name of the module name and the image where the module is located. All the above native modules are C++ modules and each module is located within its own assembly. Now that the modules are installed, they need to be registered so that they attach to the requestprocessing pipeline. The configuration section is the one that registers both native and managed modules. The modules Configuration Section The configuration section is the place where both native and managed modules get registered by the web server so that they can participate in the processing of requests inside the unified request-processing pipeline. Native modules registered in this section should have already been installed as mentioned in the previous section. Download at Boykma.Com 35 Chapter 2: IIS 7.0 and ASP.NET Integrated Mode Every module, whether it is a native or managed one, is added to the configuration section by the add element. ❑❑ name: The name attribute specifies a friendly and human readable name of the registered module. If the module registered is a native one, the name should match the same name used when the module was installed in the configuration section. In case of a managed module, any friendly and expressive name can be used. ❑❑ type: This attribute contains the value of the fully qualified namespace of the managed module registered. It applies only to managed modules. ❑❑ precondition: This attribute specifies whether the module should be loaded for all requests or only managed requests, that is, request for ASP.NET resources. If you want to enable a module to run for every request, whether it is an ASP.NET or not, simply clear this attribute in the ApplicationHost.config configuration file. In most cases, when hosting a website on a remote server you will not get the chance to play around with this file. A better solution is to configure the module through the web.config configuration file as will be shown soon. The modules defined at this level are defined globally at the web server’s level, which means all the lower-level sites, applications, and virtual directories inherit all these modules. This means all the registered modules will process any request that is part of your site, application, or virtual directory. 36 Download at Boykma.Com Chapter 2: IIS 7.0 and ASP.NET Integrated Mode To prevent this from happening, you can add a web.config configuration file into the application and remove any module that you do not want to take part in the processing of requests that belong to the application. The preceding configuration can be placed inside the application’s web.config configuration file. What the above configuration settings do is remove the installed and registered BasicAuthentication​Module. Now, regardless whether the BasicAuthenticationModule is enabled or not for the application, requests belonging to the application will not be processed by the native BasicAuthentication​Module. If, on the other hand, you want to replace an existing module with a custom module, you can simply remove the module in question and then add your own module by specifying its name. It goes without saying that the new module should be listed in the configuration section of the ApplicationHost.config configuration file. In other words, the module must be installed inside IIS 7.0 before being able to use it in your applications. It is important to know the order in which the above registered modules get executed by the IIS runtime. As previously mentioned, the configuration section registers both the native and managed modules. Every module registers itself to a specific event in the request-processing pipeline. When an event fires in the pipeline, IIS 7.0 looks at the registered modules and decides on which modules should be run (remember again, modules can be both native and managed modules). Once the selection is made, IIS then looks to see the order they are registered with in the configuration section. The order decides which module would run before another module. So it is very possible that a managed module might be placed before a native module, both registered for the same event. Therefore, the managed module will be executed first by ASP.NET runtime and then followed by the native module that will be executed within the context of IIS runtime. It is important to remember that native and managed modules are executed according to their order of appearance in the configuration section. In addition, the order can be set programmatically. However, this is available only for native modules, as you will see later on with the native AnonymousAuthenticationModule. Download at Boykma.Com 37 Chapter 2: IIS 7.0 and ASP.NET Integrated Mode The security Configuration Section Group The configuration section group is the place where you specify security options for your application. It contains two major sections, as described next. The authentication Configuration Section In the configuration section, several authentication modules were installed and then later on were registered in the configuration section. Which of these modules does IIS 7.0 use for authentication? The answer lies in the configuration section that enables/disables modules as required. Looking at the configuration section in the ApplicationHost.config configuration file yields the configuration section shown here. The installed and registered modules in the and configuration sections are shown in the configuration section above with configuration attributes. For instance, the BasicAuthenticationModule shown above with an attribute of enabled=”false” was already installed and registered in the and configuration sections. The other modules without any configurable attributes were neither installed nor registered, hence the fact that only registered and installed native modules show up in the authentication section with configurable attributes. In other words, the IIS 7.0 configuration section determines whether or not a module (authentication or any other module) will even run. The configuration section configures the behavior of each authentication type. However, the settings in the configuration section will not take effect unless the associated module has already been configured to run in the first place. The AnonymousAuthenticationModule shown previously has a set of important attributes that are worth discussing, especially from the security context. This module is usually installed as part of the default installation of IIS 7.0. For a list of the modules installed with a default installation of IIS, refer back to Chapter 1. 38 Download at Boykma.Com Chapter 2: IIS 7.0 and ASP.NET Integrated Mode This module is enabled by default and hence it applies to all requests that belong to all sites configured under the IIS 7.0 web server. The element defines userName and password attributes that are used together to specify the identity or Windows account that IIS uses when an anonymous user accesses an application when no other authentication module is enabled. By default, IIS 7.0 sets the userName attribute to the built-in account IUSR that replaces the old IUSR_MachineName account in IIS 6.0 and has minimum and limited privileges. The new IUSR built-in user account and the IIS_IUSRS built-in group are discussed in detail in Chapter 3. Moreover, you can configure IIS 7.0 to use the application pool or worker process identity as the ​ username and password for the AnonymousAuthenticationModule. This can be configured by setting an empty string for the value of userName attribute. An empty string can be represented by double quotes as follows: In case you want to disable any of the registered native modules, you can do so from inside the application’s web.config configuration file using the enabled attribute as shown here. By specifying enabled=”false” you simply disabled the module from having any role in processing a request that belongs to the current application. Another important trick to mention here is: What if you want to enable, for example, Forms AuthenticationModule to handle all content file types and not only ASP.NET resource files? In other words, what if you want to remove the preCondition attribute set for each managed module in the configuration section in the ApplicationHost.config configuration file? This can be easily done by removing the module element then adding it again without specifying a value for the preCondition attribute. Download at Boykma.Com 39 Chapter 2: IIS 7.0 and ASP.NET Integrated Mode As the preceding listing shows, the FormsAuthenticationModule element has been removed and another element has been added, setting the name attribute to FormsAuthentication. It is very important to use the same friendly name used by IIS in the ApplicationHost.config configuration file and set the type attribute to the full namespace of the FormsAuthenticationModule defined in the .NET Framework base class library. To enable all managed modules to run for all request types, you set the value of the runAllManaged​ ModulesForAllRequests attribute to true. The attribute is added to the configuration section in the configuration section group inside the application’s web.config configuration file. Note, though, that when IIS 7.0 finds out that a managed module has to be run and executed, a switch happens from a native mode to the managed mode for the managed module to be run within the ASP​ .NET runtime in the CLR. In general, it is recommended not to automatically run all managed modules for all requests. This incurs an overhead and low performance in throughput because of the switch of context. What you can do is selectively choose the managed modules that make sense for non-ASP.NET content and enable only those modules for all requests, managed and native. To decide on what authentication type to use in an application, the same old way of specifying the authentication in an ASP.NET application still works. To configure what authentication type the application should use, add an configuration section inside the configuration section group of the application’s web.config configuration file. The preceding listing configures an application to use forms authentication. However, if you want an application to be configured with Windows authentication, change the mode attribute value from Forms to a value of Windows and make sure the managed WindowsAuthenticationModule is registered correctly with IIS 7.0. Authorization Configuration Section When installing IIS 7.0, you get the chance to install a new native UrlAuthorizationModule that has been introduced to the IIS 7.0 runtime. Once this native module is installed, it shows up in both the and configuration sections of the configuration section group. 40 Download at Boykma.Com Chapter 2: IIS 7.0 and ASP.NET Integrated Mode Once the native UrlAuthorization feature is installed, IIS 7.0 configures the ApplicationHost.config configuration file and adds an entry inside the configuration section. In addition, the module gets registered by having a new entry in the configuration section. If you would like to disable this module for your application, you can easily do so by removing it from the configuration section of the configuration section group inside the application’s web.config configuration file. The native UrlAuthorizationModule uses authorization rules that determine whether the user accessing the application is authorized to access specific parts of the application or specific page URLs. These authorization rules are configured by adding some declarative rules into the configuration section inside the section group of the application’s web.config configuration file. This can be achieved by adding sub-elements inside the configuration section specifying the accessType, whether to allow or deny, the users to whom you want to grant access and finally, the verbs attribute. Moreover, when ASP.NET is installed on IIS 7.0, it registers with the ApplicationHost.config configuration file a managed UrlAuthorizationModule that is configured to run for ASP.NET resources processed by the application. Once again notice the preCondition attribute set to managedHandler, which means that this module will be invoked only for managed resources. To activate this module for all types of requests, simply use the web.config configuration file in your application to remove the module and add it again without specifying a value for the preCondition attribute. The code in the listing above removes the managed UrlAuthorizationModule element and then adds it again so that it functions against all requests processed by the IIS runtime. The managed UrlAuthorizationModule uses authorization rules that determine whether the user accessing the application is authorized to access specific parts of the application or not. These authorization rules are defined in the configuration section inside the configuration section group of the application’s web.config configuration file. There is nothing different here from what has always been used to configure ASP.NET authorization before the days of IIS 7.0. The preceding configuration settings prevent anonymous users from accessing the website. Migrating ASP.NET Applications to Integrated Mode When a new ASP.NET application is created using Visual Studio 2008 under IIS 7.0, it is by default configured to run under the Integrated mode application pool. However, having ASP.NET running for several years, there is a huge number of applications that need to be migrated in order to function properly under IIS 7.0 Integrated mode. If, however, you decide to keep your applications the same without introducing any changes to them, then simply add the application to IIS and assign it to the Classic mode application pool. With this configuration, you are sure the application will run properly the same as it did under IIS 6.0. 42 Download at Boykma.Com Chapter 2: IIS 7.0 and ASP.NET Integrated Mode When you consider migrating old applications into the Integrated mode, you should be looking at three important sections within the web.config configuration file: , , and the configuration sections. httpModules Configuration Section When an ASP.NET application wants to register an HttpModule it usually adds an entry inside the configuration section inside the configuration section group of the application’s web.config configuration file. Now to make the application run properly under IIS 7.0 Integrated mode, simply copy all the registered modules inside the configuration section into the configuration section inside the configuration section group. Not all modules registered within the configuration section will take effect when the application is running in the Integrated mode. Only those registered within the configuration section of the configuration section group will run and execute. If any new module is to be registered in an application, it is best to place it inside the configuration section of the configuration section group. C# public class BasicHttpModule : IHttpModule { public BasicHttpModule() { } public void Dispose() { // Leave it blank since we will not add any code } /// /// This method is used to register for events in the /// request-processing pipeline stages /// /// public void Init(HttpApplication context) { // Register for the BeginRequest event context.BeginRequest += new EventHandler(BeginRequest); } static void BeginRequest(object sender, EventArgs e) { HttpContext context= HttpContext.Current; context.Write(“

Welcome message from the Basic Http Module !

”); } } Download at Boykma.Com 43 Chapter 2: IIS 7.0 and ASP.NET Integrated Mode VB.NET Imports System Imports System.Web ‘’’ ‘’’ Summary description for BasicHttpModule ‘’’ Public Class BasicHttpModule Implements IHttpModule Public Sub New() ‘ ‘ TODO: Add constructor logic here ‘ End Sub #Region “IHttpModule Members” Public Sub Dispose() Implements IHttpModule.Dispose ‘ Leave it blank since we will not add any code End Sub ‘’’ ‘’’ This method is used to register for events in the ‘’’ request-processing pipeline stages ‘’’ ‘’’ Public Sub Init(ByVal context As HttpApplication) Implements IHttpModule.Init ‘ Register for the BeginRequest event AddHandler context.BeginRequest, AddressOf BeginRequest End Sub Private Shared Sub BeginRequest(ByVal sender As Object, ByVal e As EventArgs) HttpContext context= HttpContext.Current; context.Write(“

Welcome message from the Basic Http Module !

”); End Sub #End Region End Class The preceding listing shows a simple HttpModule that registers the BeginRequest event of the unified request-processing pipeline to display a message on the user’s screen. To register this module in the application’s web.config configuration file, simply add the following: 44 Download at Boykma.Com Chapter 2: IIS 7.0 and ASP.NET Integrated Mode When registering an HttpModule located in the App_Code system folder of an ASP.NET application, simply add the module by specifying a friendly name for the module and by setting the module name as a value for the type attribute. When any ASP.NET page is requested by the application, the following message is attached as the first thing to display on the page: Welcome message from the Basic Http Module! The module gets executed during the BeginRequest event during the unified request-processing pipeline. Moreover, if you move the registered module into the configuration section and keep the section inside the application’s web.config configuration file, you should turn off validation done by IIS, to ensure that the web.config configuration is valid, so that IIS does not generated migration error messages. Turning off validation simply suppresses the error messages generated. This also applies for the configuration section and enabling impersonation as you will see in the next two sections. httpHandlers Configuration Section When an ASP.NET application wants to register an HttpHandler to process a specific content file type, usually a custom one, it adds an entry into the configuration section inside the configuration section group in the application’s web.config configuration file. Now to make the application run properly under IIS 7.0 Integrated mode, simply copy all the registered handlers from inside the configuration section into the configuration section inside the configuration section group. The configuration section is originally defined inside the ApplicationHost.config configuration file. It lists all the mappings between content file extensions and their corresponding handlers. The preceding listing shows a subset of the handlers defined in the ApplicationHost.config configuration file. For instance, the PageHandlerFactory-Integrated handler maps all .aspx pages into the System.Web.UI.PageHandlerFactory class. This handler is used when an application is running in the Integrated mode. There is also a counterpart handler that runs when the application is configured in the Classic mode application pool; the handler name is PageHandlerFactory-ISAPI-2.0. Adding a custom HttpHandler for new content file types was not an easy task in IIS 6.0, and sometimes it was impossible when the IIS 6.0 server was running remotely in a hosting company. Making use of the IIS 7.0 Integrated mode processing, it is now considered a piece of cake to add a new handler for any content file type you want by simply adding a new entry into the configuration section of the configuration section group located in the application’s web.config configuration file. The preceding configuration settings add a registration entry into the configuration section. The handler entry should specify at a minimum the name of the HttpHandler; in this case, it is BasicHttpHandler. Also specified is the path attribute for which the handler will be triggered; in this 46 Download at Boykma.Com Chapter 2: IIS 7.0 and ASP.NET Integrated Mode case, all requests to content files with an extension of .info. And finally, the verb attribute is specified; in this case, the handler will accept requests with GET and POST verbs. C# using System; using System.Web; using System.Web.Security; using System.Web.UI; public class BasicHttpHandler : IHttpHandler { public BasicHttpHandler() { } #region IHttpHandler Members public bool IsReusable { get { return true;} } public void ProcessRequest(HttpContext context) { HttpResponse objResponse = context.Response; objResponse.Write(“

Thank you for visiting our info page!!”); objResponse.Write(“”); } #endregion } VB.NET Imports System Imports System.Data Imports System.Configuration Imports System.Linq Imports System.Web Imports System.Web.Security Imports System.Web.UI Imports System.Web.UI.HtmlControls Imports System.Web.UI.WebControls Imports System.Web.UI.WebControls.WebParts Imports System.Xml.Linq ‘’’ ‘’’ Summary description for BasicHttpHandler ‘’’ Public Class BasicHttpHandler Implements IHttpHandler Download at Boykma.Com 47 Chapter 2: IIS 7.0 and ASP.NET Integrated Mode Public Sub New() ‘ ‘ TODO: Add constructor logic here ‘ End Sub #Region “IHttpHandler Members” Public ReadOnly Property IsReusable() As Boolean Implements _ IHttpHandler.IsReusable Get Return True End Get End Property Public Sub ProcessRequest(ByVal context As HttpContext) Implements _ IHttpHandler.ProcessRequest() Dim objResponse As HttpResponse = context.Response objResponse.Write(“

Thank you for visiting our info page!!”) objResponse.Write(“”) End Sub #End Region End Class The preceding BasicHttpHandler code defines the HTML markup to show when a request for a page with an extension of .info is request by the client. Identity Configuration Section If the application you are migrating to work under IIS 7.0 is configured with client impersonation in the configuration section, it is recommended to disable impersonation, since the application might not behave correctly. This is especially true in that client impersonation is not available in early ASP.NET request processing stages. Alternatively, you can assign the application configured with client impersonation to an application pool with Classic mode. Figure 2-4 shows the error page when you try to run an application that enables client impersonation that is configured with an application pool set to run in the Integrated mode. When you create a new application to run under IIS 7.0, you will notice the following inside the configuration section group: The validation configuration section determines whether the IIS 7.0 runtime shall display error messages to help in upgrading the application to fit the IIS 7.0 Integrated mode. As mentioned above, if you keep the and configuration sections’ entries outside the configuration section group, they will not take effect in the unified request processing. In addition, keeping the aforementioned configuration sections inside the configuration section group does not show any error message by IIS 7.0 notifying you that there are 48 Download at Boykma.Com Chapter 2: IIS 7.0 and ASP.NET Integrated Mode errors with running the application in the Integrated mode due to the presence of undesirable sections in the application’s web.config configuration file. This is because the validateIntegratedMode​ Configuration attribute of the configuration section is set to a value of false. Changing this attribute to true will cause IIS 7.0 to show detailed error messages about the migration problems of the running application and what is recommended to fix the upgrading problems. The screen shown in Figure 2-4 would not have been displayed unless the aforementioned attribute was set to true. In other words, you can keep the , , and configuration sections in their place, while setting the validateIntegratedModeConfiguration attribute to false, or simply remove the three sections from inside the configuration section group and leave the validation attribute either true or false. Figure 2-4 This section covered the migration configuration errors that you may receive when you migrate an application to run under IIS 7.0 Integrated mode. For a more detailed discussion on the different errors you may face when migrating your application, see: http://mvolo.com/blogs/serverside/ archive/2007/12/08/IIS-7.0-Breaking-Changes-ASP.NET-2.0-applicationsIntegrated-mode.aspx Extending IIS 7.0 with Managed Handlers and Modules Throughout this book, it has been mentioned several times that the new IIS 7.0 architecture is extensible. It allows developers to extend its core functionality by developing modules or features in two flavors: ❑❑ Native code modules ❑❑ Managed code modules Download at Boykma.Com 49 Chapter 2: IIS 7.0 and ASP.NET Integrated Mode Developing native modules requires working with the C++ API. On the other hand, managed modules take advantage of the .NET framework and allow developers to use their existing ASP.NET HttpModules or develop new ones to integrate with the IIS 7.0 infrastructure and benefit from the IIS 7.0 and ASP.NET Integrated mode of execution. Managed Handlers A managed handler is an ASP.NET object that is responsible for the processing handling of an ASP.NET resource that has a specific file extension. In other words, a managed handler is linked to a particular file extension that is configured inside the configuration section of the application’s web.config configuration file. HttpHandlers do not require operating on physically available content files. The resources might be virtual ones that do not exist physically in the application. C# public class SampleHttpHandler : IHttpHandler { public SampleHttpHandler() { } public bool IsReusable { get { throw new NotImplementedException(); } } public void ProcessRequest(HttpContext context) { throw new NotImplementedException(); } } VB.NET Imports System Imports System.Data Imports System.Configuration Imports System.Linq Imports System.Web Imports System.Web.Security Imports System.Web.UI Imports System.Web.UI.HtmlControls Imports System.Web.UI.WebControls Imports System.Web.UI.WebControls.WebParts Imports System.Xml.Linq ‘’’ ‘’’ Summary description for SampleHttpHandler ‘’’ Public Class SampleHttpHandler Implements IHttpHandler Public Sub New() 50 Download at Boykma.Com Chapter 2: IIS 7.0 and ASP.NET Integrated Mode ‘ ‘ TODO: Add constructor logic here ‘ End Sub #Region “IHttpHandler Members” Public ReadOnly Property IsReusable() As Boolean Implements _ IHttpHandler.IsReusable Get Throw New NotImplementedException() End Get End Property Public Sub ProcessRequest(ByVal context As HttpContext) Implements _ IHttpHandler.ProcessRequest Throw New NotImplementedException() End Sub #End Region End Class The code in the preceding listing shows the skeleton of an HttpHandler. You create a new handler by implementing the IHttpHandler interface. This interface has two main methods to implement: ❑❑ ProcessRequest: This method is the brain of an HttpHandler. It is the method that gets executed when the handler is activated. It is responsible for handling the specific request execution and generation of the correct markup text that will be sent back to the requestor as a response. The nature of the markup text generated depends on the type of the request. For example, if the request is for an ASP.NET page, then the ProcessRequest method generates HTML markup text to be sent back to the requestor. If on the other hand the handler is configured to process and handle .xml content file extensions, then the response shall be XML markup text. This method accepts as an input parameter an instance of type HttpContext. This parameter contains the context in which the request is being processed and handled. Usually when a new request comes in, ASP.NET runtime creates a new HttpContext object. This object can store request-specific information and is disposed when the request is sent back to the requestor. ❑❑ IsReusable: This is a read-only property that returns a value of type Boolean and specifies whether the current handler can be used to process different requests for the configured resource. If the handler has some state that is expensive to initialize, and that is invariant from request to request, it should return a value of true so that ASP.NET has the opportunity to cache it. Otherwise, if the handler has nothing in common for different requests, then it is recommended to return a value of false. Developing a Managed Handler In this section, a custom managed handler is to be developed to handle displaying employees’ profile pages in an application that manages employees’ information in a department, company, you name it! The name of the handler is EmployeeHandler and will process requests for resources that have a .info extension. Download at Boykma.Com 51 Chapter 2: IIS 7.0 and ASP.NET Integrated Mode Displaying employees’ profile information can be easily done by developing an ASP.NET UserControl that serves as a template to show information about an employee. However, using a page to hold this template is a waste of resources and more processing is done with no extra benefit. For instance, when an employee’s profile page is requested, the ASP.NET runtime handles the request and fires all the events during the page’s life cycle. None of the events are of interest in this specific case and hence more processing is done with no extra benefit. In that case, to get rid of all the extra non-useful steps, an HttpHandler is recommended. The EmployeeHandler handles requests targeting specific employee as follows: http://localhost/Employees/1234.info What the handler does is extract the employee number from the URL, access the Employees data table inside the database, and if the employee is present in the database, an employee profile page is displayed with all the details from the database. The Employees data table used in this example is a simple data table used to collect information about an employee. Figure 2-5 shows the Employees data table structure. Figure 2-5 The Employees data table contains the EmployeeID, FirstName, LastName, and Email data columns. This is just an example of simple collected information. In a production environment, this would be much more serious and according to the context of the application. For the sake of this example, a new LINQ DataContext class is added to the application to represent the Employees data table in an object-like fashion, which makes querying the database an easy task. Figure 2-6 shows the new .dbml LINQ object in the solution. 52 Download at Boykma.Com Chapter 2: IIS 7.0 and ASP.NET Integrated Mode Figure 2-6 C# [Table(Name=”dbo.Employees”)] public partial class Employee : INotifyPropertyChanging, INotifyPropertyChanged { private int _EmployeeID; private string _FirstName; private string _LastName; private string _Email; public Employee() { } [Column(Storage=”_EmployeeID”, DbType=”Int NOT NULL”, IsPrimaryKey=true)] public int EmployeeID { get { return this._EmployeeID; } set { if ((this._EmployeeID != value)) { Download at Boykma.Com 53 Chapter 2: IIS 7.0 and ASP.NET Integrated Mode this._EmployeeID = value; } } } [Column(Storage=”_FirstName”, DbType=”NVarChar(50) NOT NULL”, CanBeNull=false)] public string FirstName { get { return this._FirstName; } set { if ((this._FirstName != value)) { this._FirstName = value; } } } [Column(Storage=”_LastName”, DbType=”NVarChar(50) NOT NULL”, CanBeNull=false)] public string LastName { get { return this._LastName; } set { if ((this._LastName != value)) { this._LastName = value; } } } [Column(Storage=”_Email”, DbType=”NVarChar(100)”)] public string Email { get { return this._Email; } set { if ((this._Email != value)) { this._Email = value; } } } } 54 Download at Boykma.Com Chapter 2: IIS 7.0 and ASP.NET Integrated Mode VB.NET _ Partial Public Class Employee Implements INotifyPropertyChanging, INotifyPropertyChanged Private Shared emptyChangingEventArgs As _ PropertyChangingEventArgs = New PropertyChangingEventArgs(String.Empty) Private _EmployeeID As Integer Private _FirstName As String Private _LastName As String Private _Email As String Public Sub New() MyBase.New OnCreated End Sub _ Public Property EmployeeID() As Integer Get Return Me._EmployeeID End Get Set If ((Me._EmployeeID = value) _ = false) Then Me.OnEmployeeIDChanging(value) Me.SendPropertyChanging Me._EmployeeID = value Me.SendPropertyChanged(“EmployeeID”) Me.OnEmployeeIDChanged End If End Set End Property _ Public Property FirstName() As String Get Return Me._FirstName End Get Set If (String.Equals(Me._FirstName, value) = false) Then Me.OnFirstNameChanging(value) Me.SendPropertyChanging Me._FirstName = value Me.SendPropertyChanged(“FirstName”) Me.OnFirstNameChanged End If End Set Download at Boykma.Com 55 Chapter 2: IIS 7.0 and ASP.NET Integrated Mode End Property _ Public Property LastName() As String Get Return Me._LastName End Get Set If (String.Equals(Me._LastName, value) = false) Then Me.OnLastNameChanging(value) Me.SendPropertyChanging Me._LastName = value Me.SendPropertyChanged(“LastName”) Me.OnLastNameChanged End If End Set End Property _ Public Property Email() As String Get Return Me._Email End Get Set If (String.Equals(Me._Email, value) = false) Then Me.OnEmailChanging(value) Me.SendPropertyChanging Me._Email = value Me.SendPropertyChanged(“Email”) Me.OnEmailChanged End If End Set End Property End Class This code shows the Employee object automatically generated by the LINQ DataContext object once the Employees data table is added to it. Additional generated events and properties are removed because they are not of concern when it is all about using the object to hold single employee information. This object will be used in the application to represent a single employee. The EmployeeHandler developed for this example is shown in the following code. C# public class EmployeeHandler : IHttpHandler { public EmployeeHandler() { } #region IHttpHandler Members 56 Download at Boykma.Com Chapter 2: IIS 7.0 and ASP.NET Integrated Mode public bool IsReusable { get { return false; } } public void ProcessRequest(HttpContext context) { // Determine the employee’s ID string empID = Path.GetFileNameWithoutExtension( context.Request.PhysicalPath); // Try to parse the employee’s ID int id = -1; if (!int.TryParse(empID, out id)) { context.Response.Write(“Employee ID is invalid or doesn’t exist in the database!!”); return; } // Get the employee from the database var employee = EmployeeManager.GetEmployeeByID(id); // Make sure there is an employee in // the database with the requested number if (employee == null) { // Write out an error message context.Response.Write(“Employee ID is invalid or doesn’t exist in the database!!”); return; } // Add the employee information to the Items // collection of the context context.Items[“Employee”] = employee; // Display the employee info DisplayEmployee(context, employee); } #endregion #region Utils private void DisplayEmployee(HttpContext context, Employee employee) { // Create a new page instance Page page = new Page(); // Load the employee profile usercontrol dynamically UserControl employeeCtrl = (UserControl)page.LoadControl(“~/Controls/EmployeeProfile.ascx”); // Add the control to the page instance page.Controls.Add(employeeCtrl); // Execute the page containing the usercontrol Download at Boykma.Com 57 Chapter 2: IIS 7.0 and ASP.NET Integrated Mode StringWriter writer = new StringWriter(); // Add the HTML header to the page writer.WriteLine( string.Format(“Employee {0} Profile”, employee.EmployeeID)); writer.WriteLine(“”); HttpContext.Current.Server.Execute(page, writer, false); // Add the HTML footer writer.WriteLine(“”); writer.WriteLine(“”); // Write the response out to the screen context.Response.Write(writer.ToString()); } #endregion } VB.NET Imports System Imports System.Data Imports System.Configuration Imports System.IO Imports System.Web Imports System.Web.Security Imports System.Web.UI Imports System.Web.UI.HtmlControls Imports System.Web.UI.WebControls Imports System.Web.UI.WebControls.WebParts ‘’’ ‘’’ Summary description for EmployeeHandler ‘’’ Public Class EmployeeHandler Implements IHttpHandler Public Sub New() ‘ ‘ TODO: Add constructor logic here ‘ End Sub #Region “IHttpHandler Members” Public ReadOnly Property IsReusable() As Boolean Implements _ IHttpHandler.IsReusable Get Return False End Get End Property ‘’’ ‘’’ This method handles the processing of .info requests 58 Download at Boykma.Com Chapter 2: IIS 7.0 and ASP.NET Integrated Mode ‘’’ Gets the specific employee from the database based on the ‘’’ employee id specified before the .info extension and ‘’’ fills in an employee record inside the context’s ‘’’ items collection ‘’’ ‘’’ Public Sub ProcessRequest(ByVal context As HttpContext) _ Implements IHttpHandler.ProcessRequest ‘ Determine the employee’s ID Dim empID As String = _ Path.GetFileNameWithoutExtension(context.Request.PhysicalPath) ‘ Try to parse the employee’s ID Dim id As Integer = -1 If (Not Integer.TryParse(empID, id)) Then context.Response.Write(“Employee ID is invalid” & _ “or doesn’t exist in the database!!”) Return End If ‘ Get the employee from the database Dim employee = EmployeeManager.GetEmployeeByID(id) ‘ Make sure there is an employee in ‘ the database with the requested number If employee Is Nothing Then ‘ Write out an error message context.Response.Write(“Employee ID is invalid” & _ “or doesn’t exist in the database!!”) Return End If ‘ Add the employee information to the Items ‘ collection of the context context.Items(“Employee”) = employee ‘ Display the employee info DisplayEmployee(context, employee) End Sub #End Region #Region “Utils” Private Sub DisplayEmployee(ByVal context As HttpContext, _ ByVal employee As Employee) ‘ Create a new page instance Dim page As Page = New Page() ‘ Load the employee profile usercontrol dynamically Dim employeeCtrl As UserControl = _ CType(page.LoadControl(“~/Controls/EmployeeProfile.ascx”), UserControl) ‘ Add the control to the page instance page.Controls.Add(employeeCtrl) Download at Boykma.Com 59 Chapter 2: IIS 7.0 and ASP.NET Integrated Mode ‘ Execute the page containing the usercontrol Dim writer As StringWriter = New StringWriter() ‘ Add the HTML header to the page writer.WriteLine( String.Format(“Employee {0} Profile”, _ employee.EmployeeID)) writer.WriteLine(“”) HttpContext.Current.Server.Execute(page, writer, False) ‘ Add the HTML footer writer.WriteLine(“”) writer.WriteLine(“”) ‘ Write the response out to the screen context.Response.Write(writer.ToString()) End Sub #End Region End Class This code shows the implementation of the EmployeeHandler. Each of the methods used inside the handler is explained in detail in the next few sections. IsReusable This method is inherited from the IHttpHandler interface and has been explained in detail above. C# public bool IsReusable { get { return false; } } VB.NET Public ReadOnly Property IsReusable() As Boolean Implements _ IHttpHandler.IsReusable Get Return False End Get End Property In the preceding code, it returns a value of false, which means the handler instance will not process several requests of the same extension. Every request will generate a new instance of the handler. ProcessRequest The ProcessRequest method is the bulk of the EmployeeHandler and every developed handler. This method is responsible for processing a request. For instance, when the ProcessRequest method of the .aspx handler executes, it processes the page by starting the page life cycle events. Every event will add 60 Download at Boykma.Com Chapter 2: IIS 7.0 and ASP.NET Integrated Mode some bits into the response and the end response is ready to be sent back to the requestor. The same is followed in the ProcessRequest method of the EmployeeHandler. C# // Determine the employee’s ID string empID = Path.GetFileNameWithoutExtension( context.Request.PhysicalPath); // try to parse the employee id int id = -1; if (!int.TryParse(empID, out id)) { context.Response.Write(“Employee ID is invalid or doesn’t exist in the database!!”); return; } VB.NET ‘ Determine the employee’s ID Dim empID As String = Path.GetFileNameWithoutExtension( context.Request.PhysicalPath) ‘ try to parse the employee id Dim id As Integer = -1 If (Not Integer.TryParse(empID, id)) Then context.Response.Write(“Employee ID is invalid or doesn’t exist in the database!!”) Return End If The method starts by extracting the page requested without an extension. The usual URL requested to display an employee’s profile is as follows: http://localhost/Employees/1234.info What the preceding code will do is extract the EmployeeID, which is in this case “1234”. Once the EmployeeID is extracted, it is validated to make sure the client did not request a non-integer EmployeeID. C# // Get the employee from the database var employee = EmployeeManager.GetEmployeeByID(id); // Make sure there is an employee in // the database with the requested number if (employee == null) { // Write out an error message context.Response.Write(“Employee ID is invalid or doesn’t exist in the database!!”); return; } Download at Boykma.Com 61 Chapter 2: IIS 7.0 and ASP.NET Integrated Mode VB.NET ‘ Get the employee from the database Dim employee = EmployeeManager.GetEmployeeByID(id) ‘ Make sure there is an employee in ‘ the database with the requested number If employee Is Nothing Then ‘ Write out an error message context.Response.Write(“Employee ID is invalid or doesn’t exist in the database!!”) Return End If After the EmployeeID is extracted and validated, a call to the EmployeeManager.GetEmployeeByID is issued. C# public static Employee GetEmployeeByID(int empID) { // Get a new instance of the DataContext EmployeeDataContext context = new EmployeeDataContext(); // Query the database to get the employee var query = (from e in context.Employees where e.EmployeeID == empID select e).Single(); return query; } VB.NET Public Shared Function GetEmployeeByID(ByVal empID As Integer) As Employee ‘ Get a new instance of the DataContext Dim context As EmployeeDataContext = New EmployeeDataContext() ‘ Query the database to get the employee Dim query = (From e In context.Employees Where _ e.EmployeeID = empID Select e).SingleOrDefault() Return query End Function The GetEmployeeByID method instantiates a new LINQ DataContext; in this case it is the Employee​ DataContext that was added before. Then a LINQ query is defined to select the employee record that has an EmployeeID matching that present in the requested URL. Finally, the employee record represented as Employee object is returned out of the method. Once the GetEmployeeByID method is called inside the ProcessRequest method, the returned Employee object is validated to make sure there is an employee with the requested EmployeeID in the database. If there is one, then the employee’s record is added to the Items collection of the HttpContext object. As previously explained, the HttpContext object is created at the beginning of the request 62 Download at Boykma.Com Chapter 2: IIS 7.0 and ASP.NET Integrated Mode processing and it stays valid until the response is returned back to the requestor. In this sense, the code is making use of the Items collection to store the employee’s record so that it is retrieved later on to display the details of the employee. Once the employee record is stored in the context of the request, the employee’s profile is displayed on the screen by calling the DisplayEmployee method. DisplayEmployee This method is responsible for generating the HTML markup text and sending it back to the client that initiated the request. Before diving into the details of the implementation of this method, however, let’s review the EmployeeProfile user control. C# <%@ Control Language=”C#” AutoEventWireup=”true” CodeFile=”EmployeeProfile.ascx.cs” Inherits=”Controls_EmployeeProfile” %>

Employee Profile





VB.NET <%@ Control Language=”VB” AutoEventWireup=”false” CodeFile=”EmployeeProfile.ascx.vb” Inherits=”Controls_EmployeeProfile” %>

Employee Profile




Download at Boykma.Com 63 Chapter 2: IIS 7.0 and ASP.NET Integrated Mode


The HTML part of the user control is very simple. It displays a few labels that display the details of the employee such as the EmployeeID, FirstName, LastName, and Email properties. C# public partial class Controls_EmployeeProfile : System.Web.UI.UserControl { protected void Page_Load(object sender, EventArgs e) { // Get the Employee info from the HttpContext Employee emp = (Employee)HttpContext.Current.Items[“Employee”]; // Bind the values on the screen if (emp == null) return; this.lblEmployeeID.Text = emp.EmployeeID.ToString(); this.lblFirstName.Text = emp.FirstName; this.lblLastName.Text = emp.LastName; this.lblEmail.Text = emp.Email; } } VB.NET Partial Public Class Controls_EmployeeProfile Inherits System.Web.UI.UserControl Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles Me.Load ‘ Get the Employee info from the HttpContext Dim emp As Employee = CType(HttpContext.Current.Items(“Employee”), _ Employee) ‘ Bind the values on the screen If emp Is Nothing Then Return End If Me.lblEmployeeID.Text = emp.EmployeeID.ToString() Me.lblFirstName.Text = emp.FirstName Me.lblLastName.Text = emp.LastName Me.lblEmail.Text = emp.Email End Sub End Class The code underlying usercontrol extracts the employee’s record from the same HttpContext object instance that had its Items collection filled during the ProcessRequest method, casts the data into a 64 Download at Boykma.Com Chapter 2: IIS 7.0 and ASP.NET Integrated Mode strongly typed Employee object, and finally binds each label on the usercontrol to the data retrieved from the request’s context. C# private void DisplayEmployee(HttpContext context, Employee employee) { // Create a new page instance Page page = new Page(); // Load the employee profile usercontrol dynamically UserControl employeeCtrl = (UserControl)page.LoadControl(“~/Controls/EmployeeProfile.ascx”); // Add the control to the page instance page.Controls.Add(employeeCtrl); // Execute the page containing the usercontrol StringWriter writer = new StringWriter(); // Add the HTML header to the page writer.WriteLine( string.Format(“Employee {0} Profile”, employee.EmployeeID)); writer.WriteLine(“”); HttpContext.Current.Server.Execute(page, writer, false); // Add the HTML footer writer.WriteLine(“”); writer.WriteLine(“”); // Write the response out to the screen context.Response.Write(writer.ToString()); } #endregion } VB.NET Private Sub DisplayEmployee(ByVal context As HttpContext, _ ByVal employee As Employee) ‘ Create a new page instance Dim page As Page = New Page() ‘ Load the employee profile usercontrol dynamically Dim employeeCtrl As UserControl = _ CType(page.LoadControl(“~/Controls/EmployeeProfile.ascx”), UserControl) ‘ Add the control to the page instance page.Controls.Add(employeeCtrl) ‘ Execute the page containing the usercontrol Dim writer As StringWriter = New StringWriter() Download at Boykma.Com 65 Chapter 2: IIS 7.0 and ASP.NET Integrated Mode ‘ Add the HTML header to the page writer.WriteLine( String.Format(“Employee {0} Profile”, _ employee.EmployeeID)) writer.WriteLine(“”) HttpContext.Current.Server.Execute(page, writer, False) ‘ Add the HTML footer writer.WriteLine(“”) writer.WriteLine(“”) ‘ Write the response out to the screen context.Response.Write(writer.ToString()) End Sub Going back to the DisplayEmployee method, it starts by creating a new instance of the Page class. This page object will be used as a place holder to load the EmployeeProfile usercontrol inside it. After that, the EmployeeProfile usercontrol is dynamically loaded and then added as the first and only control inside the page object. Once the usercontrol is initialized and added to the page, a new instance of the StringWriter is created to hold the HTML markup text generated by executing the page object. The page is executed by issuing a call to the HttpContext.Current.Server.Execute method. Finally, the generated HTML markup text is added to the context’s response to be sent back to the requestor. Figure 2-7 shows the employee’s profile when employee data is requested. Figure 2-7 If you notice the URL, the requested page is 5262.info, which does not exist physically inside the application. However, the EmployeeHandler intercepts the request and processes it as though it were a real page. The result shows the employee’s profile displayed on the screen without having the request go through all the page life cycle events that add nothing to the task of showing an employee’s profile from the database. 66 Download at Boykma.Com Chapter 2: IIS 7.0 and ASP.NET Integrated Mode Installing a Managed Handler Now that the handler is developed, there are two ways of installing it. One way is for the administrator or developer to use the IIS 7.0 Manager tool and add a handler mapping similar to the way it was done in the days of IIS 6.0. However, the power of developing managed handlers and deploying them in IIS 7.0 eliminates the need to access the IIS 7.0 Manager tool! This is done by simply adding the following to the application’s web.config configuration file. The new managed handler is configured by adding an entry into the handler section of the section group located in the application’s web.config configuration file. However, if the handler is to be used among several applications, then it should be installed at the IIS 7.0 web server level. The details of installing it on the web server level is out of the scope of this chapter because it is more related to IIS 7.0 administration and configuration, but you can find good walkthroughs on this topic in the book Professional IIS 7.0 and ASP.NET Integrated Programming (Wrox). The book is a complete reference on all the details about the IIS 7.0 and ASP.NET Integrated model. Managed Modules A managed module is an ASP.NET object that inherits from the IHttpModule interface. A module is used to handle the pre-processing and post-processing of a request. This means a module can register itself to process a request before it is being handed off to the managed handler for execution, and then once the request is processed by the handler, the module can again register itself to handle the request after it has been processed by the handler. The one thing to notice here is that a module does not process the request; it just registers itself to operate on the request before and after it has been operated on by the managed handler. C# public class SampleHttpModule : IHttpModule { public SampleHttpModule() { } #region IHttpModule Members public void Dispose() { throw new NotImplementedException(); } public void Init(HttpApplication context) Download at Boykma.Com 67 Chapter 2: IIS 7.0 and ASP.NET Integrated Mode { context.AuthenticateRequest += new EventHandler(context_AuthenticateRequest); } void context_AuthenticateRequest(object sender, EventArgs e) { throw new NotImplementedException(); } #endregion } VB.NET Public Class SampleHttpModule Implements IHttpModule Public Sub New() End Sub #Region “IHttpModule Members” Public Sub Dispose() Implements IHttpModule.Dispose Throw New NotImplementedException() End Sub Public Sub Init(ByVal context As HttpApplication) Implements IHttpModule.Init AddHandler context.AuthenticateRequest, AddressOf context _ AuthenticateRequest End Sub Private Sub context_AuthenticateRequest(ByVal sender As Object, _ ByVal e As EventArgs) Throw New NotImplementedException() End Sub #End Region End Class This code shows the skeleton of an HttpModule. You create a new module by implementing the ​ IHttpModule interface. This interface has one main method, Init, to implement. When the ASP.NET runtime is processing a request, several events are fired throughout the requestprocessing pipeline. Every event handles a specific task within the life cycle of the request processing. For example, there is an event to handle the authentication of the request. Based on what the application specifies for the authentication type, a specific .NET module will subscribe to the authentication event and execute its codes to authenticate the request. Because of the extensible nature of the .NET runtime, developers are allowed to build their own modules that attach to the list of events fired by the pipeline. To register any of those events, you need to create a new module and utilize the Init method to subscribe to the specific event. When an ASP.NET request is to be processed by the ASP.NET runtime, it is usually handled inside an instance of the HttpApplication object. When the runtime starts processing an ASP.NET request, it checks to see if there is a live instance of the HttpApplication object inside a pool that it maintains for all 68 Download at Boykma.Com Chapter 2: IIS 7.0 and ASP.NET Integrated Mode HttpApplication instances that the request belongs to. If there is no instance, a new HttpApplication instance is created, used by the runtime to process the request, and finally added to the pool. On the other hand, if an instance was found, it is used by the runtime to process the request. During the processing of the request, the HttpApplication instance fires a set of events such as BeginRequest, AuthenticateRequest, and so forth. Each of these events plays a role in the requestprocessing pipeline. This explains why the Init method accepts as input a parameter of type Http​ Application. The custom module can use this parameter to subscribe to the events that are exposed by the HttpApplication object. As is the case in the previous code listing, the Init method subscribes to the AuthenticateRequest event to execute some custom code. More on the HttpApplication events and request-processing pipeline is discussed in later sections. What has been said above is an old story about HttpModules. In the IIS 7.0 infrastructure, the managed HttpModule does not fire only for managed resources. On the contrary, when an application is running inside an application pool configured with the Integrated mode, all the HttpApplication events fire while processing any request, whether the request is a managed request or a native request. This has been mentioned several times and once again shows how powerful the new integration architecture is between IIS 7.0 and ASP.NET. Developing a Managed Module In this section, a custom managed module is to be developed to handle displaying formatted code, whether it is VB or C# code. Usually IIS 7.0 is configured to disable accessing a code file through a browser for security and safety reasons. However, there are times when you want to present the code files online for an article that you have posted on your blog or for some other reason. The name of the module is CodeFormatterModule and it will process requests that include the Code/CodeFileName segment in the URL. The CodeFormatterModule handles requests targeting specific code file names as follows: http://localhost/Code/Default.aspx.cs/ Notice the / at the end of the URL. Without it the RequestFilteringModule will show a 404.7 Not Found Error since accessing code files by a browser is disabled for security concerns. What the module does is extract the code file name from the URL for all requests that include the Code/ segment, and then the code file is read as a normal text file and sent back as a response to the requestor embedded in a pre tag. The formatting can be done in a better way with colorful code lines. However, for the sake of this sample, the pre tag is more than enough to clarify the idea of developing a managed module and running it under IIS 7.0. The CodeFormatterModule developed for this example is shown in the following code listing. C# public class CodeFormatterModule : IHttpModule { public CodeFormatterModule() { } #region IHttpModule Members Download at Boykma.Com 69 Chapter 2: IIS 7.0 and ASP.NET Integrated Mode public void Dispose() { } public void Init(HttpApplication context) { context.BeginRequest += new EventHandler(context_BeginRequest); } void context_BeginRequest(object sender, EventArgs e) { // Get an instance of the HttpApplication HttpApplication application = (HttpApplication)sender; // Get an instance of the HttpContext HttpContext context = application.Context; // Find if the current request ends with // Code/ClassName so that to show the class’s code // formatted on the screen. // The url to access the formatter should be something // as: http://localhost/Code/Default.aspx.cs/ // The “/” at the end is very important because without // it the RequestFiltering module installed on IIS will prevent the // the access to a .cs or .vb file. Regex regEx = new Regex(@”Code/(.*)”, RegexOptions.IgnoreCase); Match match = regEx.Match(context.Request.Path); // there is a match, this means // the request is for code formatting if (match.Success) { // Code file holds the code file name string codeFile = “”; // Split the path based on the / string[] tokens = context.Request.Path.Split(new char[] {‘/’}); if (tokens.Length <= 0) return; // Grab the code file name which should be // the item before the last one codeFile = tokens[tokens.Length-2]; // Get the physical path to the code file string pathtoCodeFile = context.Request.PhysicalPath.Replace(@”Code\”, “”); // Remove the \ from the end of the file name pathtoCodeFile = pathtoCodeFile.Substring(0, pathtoCodeFile.Length - 1); 70 Download at Boykma.Com Chapter 2: IIS 7.0 and ASP.NET Integrated Mode // If the file exists, read it and display it if (!File.Exists(pathtoCodeFile)) { // inform the user that the file doesn’t exist context.Response.Write(“File doesn’t exist!”); // End the request context.Response.End(); } // read the contents of the file string fileContent = File.ReadAllText(pathtoCodeFile); // Set the response to context.Response.ContentType = “text/html”; // Write the formatted code context.Response.Write(“
”); context.Response.Write(fileContent); context.Response.Write(“
”); // End the request context.Response.End(); } } #endregion } VB.NET Imports System Imports System.Data Imports System.Configuration Imports System.Linq Imports System.IO Imports System.Text Imports System.Text.RegularExpressions Imports System.Web Imports System.Web.Security Imports System.Web.UI Imports System.Web.UI.HtmlControls Imports System.Web.UI.WebControls Imports System.Web.UI.WebControls.WebParts Imports System.Xml.Linq ‘’’ ‘’’ Summary description for CodeFormatterModule ‘’’ Public Class CodeFormatterModule Implements IHttpModule Public Sub New() ‘ Download at Boykma.Com 71 Chapter 2: IIS 7.0 and ASP.NET Integrated Mode ‘ TODO: Add constructor logic here ‘ End Sub #Region “IHttpModule Members” Public Sub Dispose() Implements IHttpModule.Dispose End Sub ‘ Subscribe to the BeginRequest to process ‘ a request to the code formatter so that ‘ if the request was to show formatted code, ‘ the rest of the HttpApplication events get ‘ ignored, hence improving performance. Public Sub Init(ByVal context As HttpApplication) Implements IHttpModule.Init AddHandler context.BeginRequest, AddressOf context_BeginRequest End Sub Private Sub context_EndRequest(ByVal sender As Object, ByVal e As EventArgs) End Sub Private Sub context_BeginRequest(ByVal sender As Object, ByVal e As EventArgs) ‘ Get an instance of the HttpApplication Dim application As HttpApplication = CType(sender, HttpApplication) ‘ Get an instance of the HttpContext Dim context As HttpContext = application.Context ‘ Find if the current request ends with ‘ Code/ClassName so that to show the class’s code ‘ formatted on the screen. ‘ The url to access the formatter should be something ‘ as: http://localhost/Code/Default.aspx.cs/ ‘ The “/” at the end is very important because without ‘ it the RequestFiltering module installed on IIS will prevent the ‘ the access to a .cs or .vb file. Dim regEx As Regex = New Regex(“Code/(.*)”, RegexOptions.IgnoreCase) Dim match As Match = regEx.Match(context.Request.Path) ‘ There is a match, which means ‘ the request is for code formatting If match.Success Then ‘ Code file holds the code file name Dim codeFile As String = “” ‘ Split the path based on the / Dim tokens As String() = context.Request.Path.Split(New Char() {“/”c}) If tokens.Length <= 0 Then Return End If ‘ Grab the code file name which should be ‘ the item before the last one 72 Download at Boykma.Com Chapter 2: IIS 7.0 and ASP.NET Integrated Mode codeFile = tokens(tokens.Length - 2) ‘ Get the physical path to the code file Dim pathtoCodeFile As String = _ context.Request.PhysicalPath.Replace(“Code\”, “”) ‘ Remove the \ from the end of the file name pathtoCodeFile = pathtoCodeFile.Substring(0, pathtoCodeFile.Length - 1) ‘ If the file exists, read it and display it If (Not File.Exists(pathtoCodeFile)) Then ‘ Inform the user that the file doesn’t exist context.Response.Write(“File doesn’t exist!”) ‘ End the request context.Response.End() End If ‘ Read the contents of the file Dim fileContent As String = File.ReadAllText(pathtoCodeFile) ‘ Set the response to context.Response.ContentType = “text/html” ‘ Write the formatted code context.Response.Write(“
”) context.Response.Write(fileContent) context.Response.Write(“
”) ‘ End the request context.Response.End() End If End Sub #End Region End Class This code shows the implementation of the CodeFormatterModule. Each of the methods used inside the module is explained in detail in the next few sections. Init The Init method is the core of the HttpModule. It is the best place inside the HttpModule to subscribe to the HttpApplication’s events. The module above subscribes to the BeginRequest event. C# public void Init(HttpApplication context) { context.BeginRequest += new EventHandler(context_BeginRequest); } Download at Boykma.Com 73 Chapter 2: IIS 7.0 and ASP.NET Integrated Mode VB.NET Public Sub Init(ByVal context As HttpApplication) Implements IHttpModule.Init AddHandler context.BeginRequest, AddressOf context_BeginRequest End Sub Subscribing to an event is done the usual way in the .NET framework. The context_BeginRequest method is the method to be called when the HttpApplication’s BeginRequest event fires. Context_BeginRequest The context_BeginRequest method does the bulk of the processing by extracting the code file name and displaying the code inside it as formatted code. C# // Get an instance of the HttpApplication HttpApplication application = (HttpApplication)sender; // Get an instance of the HttpContext HttpContext context = application.Context; VB.NET ‘ Get an instance of the HttpApplication Dim application As HttpApplication = CType(sender, HttpApplication) ‘ Get an instance of the HttpContext Dim context As HttpContext = application.Context The method starts by getting a reference to the HttpApplication and the HttpContext objects. C# Regex regEx = new Regex(@”Code/(.*)”, RegexOptions.IgnoreCase); Match match = regEx.Match(context.Request.Path); // There is a match, which means // the request is for code formatting if (match.Success) { VB.NET Dim regEx As Regex = New Regex(“Code/(.*)”, RegexOptions.IgnoreCase) Dim match As Match = regEx.Match(context.Request.Path) ‘ There is a match, which means ‘ the request is for code formatting If match.Success Then Then the URL of the current request is matched against a pattern that contains the Code/ segment. If the URL has such a token, this means that the requestor is trying to access a code file. 74 Download at Boykma.Com Chapter 2: IIS 7.0 and ASP.NET Integrated Mode C# // Code file holds the code file name string codeFile = “”; // Split the path based on the / string[] tokens = context.Request.Path.Split(new char[] {‘/’}); if (tokens.Length <= 0) return; // Grab the code file name which should be // the item before the last one codeFile = tokens[tokens.Length-2]; // Get the physical path to the code file string pathtoCodeFile = context.Request.PhysicalPath.Replace(@”Code\”, “”); // Remove the \ from the end of the file name pathtoCodeFile = pathtoCodeFile.Substring(0, pathtoCodeFile.Length - 1); VB.NET ‘ Code file holds the code file name Dim codeFile As String = “” ‘ Split the path based on the / Dim tokens As String() = context.Request.Path.Split(New Char() {“/”c}) If tokens.Length <= 0 Then Return End If ‘ Grab the code file name which should be ‘ the item before the last one codeFile = tokens(tokens.Length - 2) ‘ Get the physical path to the code file Dim pathtoCodeFile As String = _ context.Request.PhysicalPath.Replace(“Code\”, “”) ‘ Remove the \ from the end of the file name pathtoCodeFile = pathtoCodeFile.Substring(0, pathtoCodeFile.Length - 1) The code file name is extracted from the URL, and the Code/ segment is removed from the URL, since this is a virtual token and does not exist in the application. It is only used to distinguish requests for normal resources and requests for code files. After that, the physical path of the code file is retrieved. C# // If the file exists, read it and display it if (!File.Exists(pathtoCodeFile)) { // Inform the user that the file doesn’t exist Download at Boykma.Com 75 Chapter 2: IIS 7.0 and ASP.NET Integrated Mode context.Response.Write(“File doesn’t exist!”); // End the request context.Response.End(); } // Read the contents of the file string fileContent = File.ReadAllText(pathtoCodeFile); // Set the response to context.Response.ContentType = “text/html”; // Write the formatted code context.Response.Write(“
”); context.Response.Write(fileContent); context.Response.Write(“
”); // End the request context.Response.End(); VB.NET ‘ If the file exists, read it and display it If (Not File.Exists(pathtoCodeFile)) Then ‘ Inform the user that the file doesn’t exist context.Response.Write(“File doesn’t exist!”) ‘ End the request context.Response.End() End If ‘ Read the contents of the file Dim fileContent As String = File.ReadAllText(pathtoCodeFile) ‘ Set the response to context.Response.ContentType = “text/html” ‘ Wite the formatted code context.Response.Write(“
”) context.Response.Write(fileContent) context.Response.Write(“
”) ‘ End the request context.Response.End() If the file exists, the content of the file is read into a string, the response’s content type is set to text/html, and then a pre tag is inserted into the response stream, followed by the content of the code file, and finally a closing pre tag. Figure 2-8 shows the result of accessing a code file in an application. 76 Download at Boykma.Com Chapter 2: IIS 7.0 and ASP.NET Integrated Mode Figure 2-8 Figure 2-8 shows the content of the Default.aspx.cs code file displayed as normal text. Installing a Managed Module Now that the module is developed, there are two ways of installing it. One of the ways is to go to the IIS 7.0 Manager tool and install the module as a managed one that can be applied to any application hosted on the web server. The other way is to just add an entry to the section of the configuration section group inside the application’s web.config configuration file. As mentioned previously, if the module is to be executed for any application hosted on the IIS 7.0 web server, it is recommended to install it at the web server level. The details of installing it at the web server level are out of the scope of this chapter. (See the reference to the book mentioned earlier for details about IIS 7.0 and ASP.NET Integrated mode.) Summary In this chapter you were introduced to the new IIS 7.0 Integrated mode of execution. The new mode unifies the request-processing pipeline between IIS 7.0 infrastructure and ASP.NET runtime, thus leveraging ASP.NET from a framework to develop web applications to a framework to extend IIS 7.0 runtime. Download at Boykma.Com 77 Chapter 2: IIS 7.0 and ASP.NET Integrated Mode While upgrading an ASP.NET application from previous versions of IIS to host it under IIS 7.0 Integrated mode, it is essential to consider several sections within the application’s web.config configuration file to get rid of the inconsistencies and abide by the new rules. Some of the new rules are as follows: ❑❑ httpModules section: It is recommended to remove this section from the configuration section group file of the application’s web.config configuration file and place its entries inside the configuration section of the configuration section group. Only modules defined inside the section will take effect and execute. Every managed module defined can be configured to run for either all resources or just ASP.NET resources. ❑❑ httpHandlers section: The httpHandlers section should also be removed from the configuration section group of the application’s web.config configuration file, with its entries placed inside the configuration section inside the configuration section group. What is important about configuring a handler is that there is no need to use the IIS 7.0 Manager tool to map a content file extension to a specific handler in an application; only a single configuration entry is required and that’s it! ❑❑ identity section: Impersonation is not allowed during the early stages of a request processing and that is why an application should either turn off impersonation or upgrade the application and make it run under the Classic mode. Impersonation could be kept on and at the same time set the value of the validateIntegratedModeConfiguration attribute on the validation section to false that is located inside the configuration section inside the configuration section group. The main point to keep in mind about the new Integrated mode is the extensibility IIS 7.0 offers for ASP.NET developers. IIS 7.0 HTTP request processing integrates itself with ASP.NET to form a unified request-processing pipeline. Both IIS 7.0 and ASP.NET respond to the same events at the same time and IIS decides on the native and managed modules to run and execute. The unified request-processing pipeline gives ASP.NET services and modules the ability to handle and process any resource and not just ASP.NET resources. You can now enable FormsAuthentication module to protect images, .php, .html, .asp resources, etc. in an application without having to do any workaround to make this happen. Moreover, you can now develop ASP.NET HttpModules and HttpHandlers, configure them through the application’s web.config configuration file, and let them take part of the request processing without having to do any configuration settings using the IIS 7.0 Manager tool. The next chapter continues this discussion to explore the security context of a request when it is processed inside the unified IIS 7.0 Integrated mode. The discussion is focused on the authentication and authorization events that get fired through the life cycle of the unified request-processing pipeline. In addition, the new IUSR built-in user and IIS_IUSRS built-in group will be explained in detail, showing their advantages and portability when deploying applications. 78 Download at Boykma.Com 3 HTTP Request Processing in IIS 7.0 Integrated Model The previous chapter discussed the architecture of the new IIS 7.0 integrated mode in detail. This chapter starts by introducing the advantages, the new IUSR built-in account and IIS_IUSRS built-in group, replacing the old IUSR_MACHINENAME user account and IIS_WPG group. The chapter continues to describe security-related processing that occurs each time the unified request-processing pipeline processes a request. A combination of the application’s configuration in IIS and the ASP.NET configuration for the application determines the security context that is initialized for each request. Once a request enters IIS 7.0, the first defense gate takes control to validate the request before starting the unified request-processing pipeline. Once accepted, the unified pipeline starts processing and handling the request. The added value of the new IIS 7.0 integrated mode is that IIS and ASP.NET both subscribe to the same events fired during the processing of the request. After a request is running through the unified pipeline, the authentication and authorization options that have been configured for the application take affect. If a request passes authentication and authorization checks, there is still one last hurdle to clear; the HttpHandler that is assigned to process the request, in case the request is an ASP.NET resource. In this chapter, you will learn about: ❑❑ The new IUSR built-in account and IIS_IUSRS built-in group. ❑❑ How the security identity of a request is constructed during the unified processing. ❑❑ Security issues around the ASP.NET asynchronous programming model. ❑❑ Authentication steps that occur in the HTTP pipeline. ❑❑ Authorization processing in the HTTP pipeline. ❑❑ How the new IIS native filtering module controls access to files. Download at Boykma.Com Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model Built-in IUSR Account and IIS_IUSRS Group Before going on to start analyzing the security context of requests while they take the journey through the IIS 7.0 and ASP.NET unified request-processing pipeline, it is important to give an overview on the new IUSR account and IIS_USRS group. IIS 7.0 introduces a new built-in account IUSR and a built-in group IIS_IUSRS. The IUSR account replaces the old IUSR_MachineName account that was used previously by the IIS 6.0 web server. The new account is a built-in account, which means its password never expires and hence this improves deployment by not having to worry about password differences between the local IIS_IUSR MachineName account and the remote user account. Another benefit of the new IUSR account is when you set access control lists (ACLs) for the IUSR account folders inside your application, there is no need to worry about copying these ACLs from your local machine to the remote web server machine. The reason lies behind the fact that the operating system creates unique security identifiers (SIDs) for every account created in Windows, and ACLs are applied on the SID of the account and not anything else. This means when you apply ACLs locally on the IUSR for a folder in your application, those ACLs will be copied with the folder when moved from the local server to the remote web server and the same ACLs will take effect, since all Windows machines that have IIS 7.0, whether the client IIS 7.0 or server IIS 7.0, share the same SID for the IUSR account. An important feature to mention about the new IUSR account is that it acts anonymously on the network. This means when you try to access resources located somewhere on the network from inside your application, you need to impersonate some other account that the network recognizes as a machine user account that can authenticate against it. One could use the NT AUTHORITY\NETWORK SERVICE account that acts as a machine account and can be authenticated. This limitation on the IUSR account has been done as a security precaution so as not to elevate the privileges of the IUSR account. On the other hand, the IIS_IUSRS group replaces the old IIS_WPG group. This group has been granted the necessary permissions on all necessary files and resources so that when an account is attached to this group, it can act as a normal application pool worker process identity without the need for any additional action. Whatever applies on the IUSR account regarding setting of the ACL applies also to the IIS_IUSRS group. If you apply ACLs to this group on your files and folders and then move your application from your local web server to the remote one, the same ACLs are also copied. This is due to the fact that Windows operating system applies ACLs based on the SID of the group or user account. Moreover, when IIS 7.0 starts a new worker process, it usually passes a token that is going to be used as the identity of the worker process. In IIS 7.0, the default identity that the application pool uses is the NT AUTHORITY\NETWORK SERVICE and this has been configured in the section group inside the ApplicationHost .config configuration file. 80 Download at Boykma.Com Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model The good news is that if you configure the application pool to run with a custom user account, no matter what the account is, IIS 7.0 infrastructure automatically adds at run time the worker process token or identity, no matter what the account is, to the IIS_IUSRS group and hence there is no need to worry about giving the worker process identity account the necessary privileges to function properly. Integrated Mode Per-Request Security It has been previously mentioned how the request-processing pipeline gets unified when an ASP.NET application is running in the IIS 7.0 integrated mode. It is clear how the duplication of effort has been eliminated since both ASP.NET and IIS 7.0 now share the same request-processing pipeline. The unified pipeline indicates that at every stage in the pipeline, IIS and ASP.NET modules subscribe to the same event and hence they run side by side. IIS runtime will check the configured modules and executes them according to their order of listing inside the configuration section with some exceptions. The native modules have the capability to change the order programmatically, which is the case with the native AnonymousAuthenticationModule. This module always runs at the end of the authentication event fired by the pipeline no matter what other authentication modules are enabled. As you will see later, this proves why the managed WindowsAuthenticationModule does not fire its Authenticate event since AnonymousAuthenticationModule, when enabled, fires after the managed WindowsAuthenticationModule. The configured modules for a specific pipeline event could include both native and managed modules. For instance, the FormsAuthenticationModule has been integrated into the authentication modules in IIS. This allows you now to enable this module for your application from inside the IIS Manager tool. When the managed FormsAuthenticationModule is enabled, no other native authentication module can be enabled at the same time except that of the AnonymousAuthenticationModule. Therefore, while an application is running in the integrated mode, IIS can execute only a single authentication module at once with the exception of the AnonymousAuthenticationModule that gets executed after all configured authentication modules have been executed. This is quite different from what has been happening in the pre-releases of IIS 7.0. In IIS 6.0, for example, the request has to pass first through the IIS request-processing pipeline. After it has been handed off to the ASP.NET ISAPI extension, another processing pipeline starts, this time in the boundaries of the .NET Framework. From an ASP.NET perspective, the security choices in IIS boil down to the following: ❑❑ Does the ASP.NET application require a WindowsPrincipal for each user that authenticates with the website? ❑❑ Will ASP.NET handle authentication using forms-based authentication, or some other custom authentication strategy? ❑❑ Will the ASP.NET site run from a remote file share, that is, a share defined with a Universal Naming Convention (UNC) name? This question is related to the previous two considerations because using a UNC share is primarily a deployment decision, but one that does have ramifications for security. Download at Boykma.Com 81 Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model From a technical perspective, when IIS 7.0 starts a new worker process to initiate the execution of the request, it passes a token to the worker process. Usually, it is the NT AUTHORITY\NETWORK SERVICE token that is configured in the ApplicationHost.config configuration file as mentioned previously. Once the worker process is initiated, if the application is an ASP.NET application, a list of Application Domains called the application domain pool is checked to see if the application targeted has an active application domain to load. If there is no active application domain for the current application, a new application domain is created and then the CLR is loaded inside the new application domain. After that the unified request-processing pipeline starts execution, the different events start firing, and the different modules start executing according to the events they have already been registered for. Both IIS 7.0 and ASP.NET subscribe to the same events and hence IIS 7.0 and ASP.NET processing for the same event happens at the same time. In IIS 7.0, the following directory security options are available: ❑❑ Authenticated access using Windows Security (either NTLM- or Kerberos-based), Basic Authentication, Digest Authentication ❑❑ Authenticated access using certificate mapping ❑❑ Anonymous access The first two security configurations result in a security token that represents a specific user from either the local machine’s security database or a domain. The token returned varies from request to request, depending on which user is currently making a request to IIS. The last option also results in a security token representing a specific user; however, on every request made to IIS, the same security token is returned because IIS uses a fixed identity to represent an anonymous user, which is the IUSR account by default. However, it can be any other user account or even the account configured on the worker process. When a new request enters the unified request-processing pipeline and the stage where authentication should take place is reached, a check is done on both the authentication type the application is configured to run with (Forms, Windows, and None) and the authentication modules enabled on IIS. In determining the authenticated identity of a request, IIS takes the following considerations: ❑❑ If a username/password is configured at the application or virtual directory level, it is used as the identity of the current request, which is the impersonation token. To configure a fixed account on an application or virtual directory, do the following: 1. Open the IIS 7.0 Manager tool. 2. Select the specific application or virtual directory. 3. Click on Advanced Settings. 4. Edit the Physical Path Credentials field. ❑❑ Figure 3-1 shows the window used to configure a fixed user for the anonymous identity on an application or virtual directory. ❑❑ The figure shows the window that is used to configure a specific user account on the application or virtual directory. On the other hand, you can select not to impersonate the access to the application or virtual directory to any user account, instead using the impersonation token generated later on by the IIS runtime. 82 Download at Boykma.Com Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model Figure 3-1 ❑❑ If the application or virtual directory is not configured with fixed user credentials, IIS checks the type of authentication enabled on the application, whether it is Windows, Basic, Digest, or anonymous authentication. If the AnonymousAuthenticationModule is enabled, it will automatically rule over all the other enabled native authentication modules and no negotiation happens between IIS and the user. The issue is different when authorization rules are set on the application. At this time, IIS postpones the execution of the AnonymousAuthenticationModule to the end of the authentication stage of the unified request-processing pipeline. However, if the AnonymousAuthenticationModule is disabled and any other native authentication module is enabled, IIS would request a username/password to authenticate the request. If the request is authenticated successfully, an impersonation token is generated and stored by IIS to be accessed later by the managed WindowsAuthenticationModule, in the case that the ASP.NET application is running in the IIS 7.0 classic mode. However, if the application is running in IIS 7.0 integrated mode and a user has been authenticated, the native authentication module sets the value of the native User principal, and then the integrated request processing pipeline proxies that native User principal to managed code automatically. In other words, if a native authentication module sets the value of the native User principal, it gets brought over to ASP.NET as the HttpContext.Current.User. In addition, IIS sets the value of the server variable LOGON_USER to the username that was used in the authentication process. This is done for both modes of processing in IIS 7.0: Classic and Integrated. ❑❑ However, if IIS finds out that all the native authentication modules are disabled, not mentioning the AnonymousAuthenticationModule, the impersonation token generated is that of the default identity assigned for the native AnonymousAuthenticationModule or any custom identity, and there will be no value set for the managed HttpContext.Current.User property when the Download at Boykma.Com 83 Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model application is running in the IIS 7.0 Integrated mode. This implies that the managed Windows​ AuthenticationModule will not fire its Authenticate event. You will see why in much more detail when you reach the section on the managed WindowsAuthenticationModule later in the chapter. ❑❑ On the other hand, if an impersonation token is generated by IIS runtime and the ASP.NET application is configured to run under IIS 7.0 classic mode, the managed WindowsAuthentication​ Module grabs the impersonation token from IIS and generates the WindowsPrincipal object and sets the value of the User property on the HttpContext class, all based on the received impersonation token. Finally it triggers its Authenticate event. In the case of an application running in IIS 7.0 Integrated mode, the managed WindowsAuthenticationModule ignores the impersonation token set by IIS 7.0 and simply extracts a WindowsPrincipal instance from the Http​ Context.Current.User and creates a new instance of the WindowsIdentity class based on the Identity property located at the WindowsPrincipal class extracted from the HttpContext​ .Current.User property. The module then decides if there is a valid WindowsIdentity instance (i.e. the request is not anonymous and authentication took place inside a native module), and triggers its Authenticate event; otherwise, the Authenticate event will not get a chance to be fired. As mentioned above, when IIS 7.0 detects that the ASP.NET application is running in IIS 7.0 Integrated mode, the integrated request-processing pipeline automatically maps the authenticated user represented by a native User principal, if any, to the HttpContext.Current​ .User property to be accessible by the managed WindowsAuthenticationModule. Thus, you can conclude that you can simply remove the managed WindowsAuthenticationModule and the HttpContext.Current.User will always be set in case authentication took place inside IIS 7.0’s native authentication modules. ❑❑ At this stage, if the AnonymousAuthenticationModule is enabled, it executes. After its execution, the impersonation token is generated based on the identity set on the Anonymous​ AuthenticationModule. In addition, if the application is running inside IIS 7.0 Integrated mode, the HttpContext.Current.User’s value is set to a dummy instance of the Windows​ Principal class with its Identity.Name property set to an empty string. ❑❑ At the end of the authentication stage, IIS gets the value of the impersonation token generated by either a native authentication module, in case the AnonymousAuthenticationModule is disabled, or by the AnonymousAuthenticationModule in case it is enabled. The value of the impersonation token is stored by IIS so that it can be accessed later. Moreover, if the application is running under the IIS 7.0 Integrated mode, at the end of the authentication stage, the Http​ Context.Current.User’s value would also have been set to a complete WindowsPrincipal instance if the native AnonymousAuthenticationModule was disabled, and it gets set to a dummy instance of the WindowsPrincipal class with its Identity.Name property set to an empty string if the native AnonymousAuthenticationModule was enabled and got executed. ❑❑ If there is no authenticated user, this is the case when the native AnonymousAuthenticationModule is enabled with or without the managed FormsAuthenticationModule. ASP.NET automatically handles the authentication of the request with the managed FormsAuthentication​ Module if enabled. Regardless of the user account configured for the AnonymousAuthentication​ Module, IIS will use the identity of the worker process as the impersonation token, which is by default the NT AUTHORITY\NETWORK SERVICE. If the request is successfully authenticated, IIS stores the username used in the authentication process into the server variable LOGON_USER. Usually, the enabled configured native module executes within the boundaries of the web server core engine, and any enabled managed authentication module is handed off to ASP.NET runtime to process. Just because an impersonation token might be generated by IIS 7.0 or the HttpContext.Current.User’s 84 Download at Boykma.Com Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model value might be set by the integrated-request processing pipeline and is available to ASP.NET does not mean that the same security credentials will be used by ASP.NET. Instead, the security context for each request is dependent on the following settings and information: ❑❑ The identity of the operating system thread ❑❑ The request authenticated identity from IIS ❑❑ The value of the impersonate attribute in the configuration element ❑❑ The value of the username and password attributes in the configuration element ❑❑ Whether the mode attribute of the configuration element has been set to Windows Before diving into how these settings interact with each other, an understanding of the AnonymousAuthenticationModule is required, as well as a review of where security information can be stored. ❑❑ Native AnonymousAuthentication Module This module is configured with the new built-in IUSR account. This can be configured in both the ApplicationHost.config configuration file and the configuration section group of the application’s web.config configuration file. You are given the choice to set the anonymous user account to the same identity used by the worker process inside an application pool that is the NT AUTHORITY\NETWORK SERVICE account. To change the default user, right-click the Anonymous Authentication method listed in the Authentication section. Figure 3-2 shows the window that you can use to edit the identity assigned to the module. Figure 3-2 Download at Boykma.Com 85 Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model You can assign the Application Pool Identity as the identity of the anonymous user. In addition, you can click the Set… button and a small window pops up allowing you to specify a Windows account of your choice to use as the anonymous user identity. When the Application Pool Identity account is selected, you will notice the following change in the application’s web.config configuration file: However, if you have chosen to set the anonymous user identity to another Windows account of choice, the following would appear in the application’s web.config configuration file: As you can see, both the userName and password attributes have been set to the custom user account that you have specified. It is important to mention that the native AnonymousAuthenticationModule runs during the authentication stage of the unified request-processing pipeline, and it is the last module to run in this stage. This is the programmatic reordering that was mentioned before when it comes to running different modules, managed and native, at the same stage of the pipeline. It is true that IIS runs the modules according to the order of appearance in the section with the exception of programmatic ordering that only native modules have the right to make use of. In addition, if the AnonymousAuthenticationModule is enabled, regardless of the other native authentication modules, the request will be considered anonymous and none of the native authentication modules would run. Remember that the AnonymousAuthenticationModule is executed by the IIS core engine at the end of the authentication stage after all other authentication modules, native or managed, have executed. Here are some scenarios on how the native AnonymousAuthenticationModule works: ❑❑ When the native AnonymousAuthenticationModule is enabled, it rules over all other native authentication modules. For this scenario, assume the managed FormsAuthenticationModule is disabled, the ASP.NET application is configured with Windows authentication, and all other native authentication modules are disabled. Hence, an impersonation token is generated based on the user identity assigned for the AnonymousAuthenticationModule, which is by default the IUSR account. In addition, if IIS 7.0 detects that the application is running under the Integrated mode, the HttpContext.Current.User property is set to a dummy instance of the WindowsPrincipal class. ❑❑ If the native AnonymousAuthenticationModule is enabled and any other native authentication is also enabled, such as the Basic or Windows authentication modules, nothing changed to what has been mentioned above. IIS 7.0 knows that the native AnonymousAuthenticationModule is enabled and does not request any username/password from the user. ❑❑ In this last case, if the native AnonymousAuthenticationModule is enabled and the managed FormsAuthenticationModule is also enabled, the FormsAuthenticationModule will check if there is a valid user on the HttpContext.Current.User property. If not, it creates a new GenericPrincipal instance and assigns it to the HttpContext.Current.User property, along 86 Download at Boykma.Com Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model with other tasks you will learn more about in Chapter 6, “Forms Authentication.” The native AnonymousAuthenticationModule would still generate the impersonation token based on the default identity assigned for the module itself or any other custom identity. Where Is the Security Identity for a Request? In reality, no single location in ASP.NET defines the identity for a request. This is a case where the differences between the older Win32-oriented programming model and the managed world sort of collide. Before the .NET Framework was implemented, the question of security identity always rested with the currently executing operating system thread. An operating system thread always has a security token associated with it representing either a local (potentially a built-in identity) or a domain account. Win32 programmers have always had the ability to create new security tokens and use these to change the security context of an operating system thread. This behavior includes reverting the identity of a thread and explicitly impersonating a security identity. With the introduction of the .NET Framework, a managed representation of a thread is available from the System.Threading.Thread class. The Thread class has a CurrentPrincipal property that represents the security identity of the managed thread. It is entirely possible for the security identity of the operating system thread (obtainable by calling System.Security.Principal.WindowsIdentity​ .GetCurrent()) to differ in type and in value from the managed IPrincipal reference available from an instance of Thread.CurrentPrincipal. As if that was not complicated enough, ASP.NET introduced the concept of an HttpContext associated with each request flowing through ASP.NET. The HttpContext instance for a request has a User property that also contains a reference to an IPrincipal implementation. This additional reference to a security identity opened up the possibility of having a third set of security credentials available to a developer that differed from the information associated with the operating system thread and the managed thread. To demonstrate, the following example is a simple application that displays three different identities. The sample code stores the operating system’s security identity and the managed thread identity as they exist during the Application_BeginRequest event, and when a page is running. The value for the User property on the HttpContext is also stored. The initial identity information is collected in a managed SecurityIdentitiesModule developed for the sake of this demonstration: C# void context_BeginRequest(object sender, EventArgs e) { HttpContext current = HttpContext.Current; current.Items[“OperatingSystem_ThreadIdentity_BeginRequest”] = WindowsIdentity.GetCurrent().Name; Download at Boykma.Com 87 Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model if (String.IsNullOrEmpty(Thread.CurrentPrincipal.Identity.Name)) { current.Items[“ManagedThread_ThreadIdentity_BeginRequest”] = “[null or empty]”; current.Items[“ManagedThread_IsGenericPrincipal”] = (Thread.CurrentPrincipal is GenericPrincipal); } else current.Items[“ManagedThread_ThreadIdentity_BeginRequest”] = Thread.CurrentPrincipal.Identity.Name; if (current.User == null) current.Items[“HttpContext_User_BeginRequest”] = “[null]”; else current.Items[“HttpContext_User_BeginRequest”] = current.User.Identity.Name; } VB.NET Private Sub context_BeginRequest(ByVal sender As Object, ByVal e As EventArgs) Dim current As HttpContext = HttpContext.Current current.Items(“OperatingSystem_ThreadIdentity_BeginRequest”) _ = WindowsIdentity.GetCurrent().Name() If String.IsNullOrEmpty(Thread.CurrentPrincipal.Identity.Name) Then current.Items(“ManagedThread_ThreadIdentity_BeginRequest”) _ = “[null or empty]” current.Items(“ManagedThread_IsGenericPrincipal”) = _ (TypeOf Thread.CurrentPrincipal Is GenericPrincipal) Else current.Items(“ManagedThread_ThreadIdentity_BeginRequest”) = _ Thread.CurrentPrincipal.Identity.Name() End If If current.User Is Nothing Then current.Items(“HttpContext_User_BeginRequest”) = “[null]” Else current.Items(“HttpContext_User_BeginRequest”) = _ current.User.Identity.Name() End If End Sub This code contains checks for null or empty strings because Application_BeginRequest occurs as the first event in the integrated unified request-processing pipeline. As a result, neither IIS nor ASP. NET has configured any security context for the current request. From the ASP.NET point of view, it has not attempted to associate an IPrincipal with the current HttpContext. Additionally, ASP.NET has not synchronized user information on the HttpContext to the current managed thread. The managed thread principal is instead associated with an instance of a System.Security.Principal.Generic​ Principal with a username set to the empty string. The value of the User property on the HttpContext though is not even initialized, and returns a null value instead. 88 Download at Boykma.Com Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model The values for this information are displayed in a page load event using the following code: C# using System; using System.Security.Principal; using System.Threading; public partial class _Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { Response.Write(“The OS thread identity during BeginRequest is: “ + Context.Items[“OperatingSystem_ThreadIdentity_BeginRequest”] + “
”); Response.Write(“The managed thread identity during BeginRequest is: “ + Context.Items[“ManagedThread_ThreadIdentity_BeginRequest”] + “
”); Response.Write(“The managed thread identity during BeginRequest is “ + “a GenericPrincipal: “ + Context.Items[“ManagedThread_IsGenericPrincipal”] + “
”); Response.Write(“The user on the HttpContext during BeginRequest is: “ + Context.Items[“HttpContext_User_BeginRequest”] + “
”); Response.Write(“
”); Response.Write(“The OS thread identity when the page executes is: “ + WindowsIdentity.GetCurrent().Name + “
”); if (String.IsNullOrEmpty(Thread.CurrentPrincipal.Identity.Name)) Response.Write(“The managed thread identity when” + “the page executes is: “ + “[null or empty]” + “
”); else Response.Write(“The managed thread identity when the “ + “page executes is: “ + Thread.CurrentPrincipal.Identity.Name + “
”); Response.Write(“The managed thread identity is of type: “ + Thread.CurrentPrincipal.ToString() + “
”); if (String.IsNullOrEmpty(User.Identity.Name)) Response.Write(“The user on the HttpContext when “ + “the page executes is: “ + “[null or empty]” + “
”); else Response.Write(“The user on the HttpContext when the “ + “page executes is:“ + User.Identity.Name + “
”); Response.Write(“The user on the HttpContext is of type: “ + User.ToString() + “
”); Response.Write(“The user on the HttpContext and the “ + “thread principal point at the same object: “ + Download at Boykma.Com 89 Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model (Thread.CurrentPrincipal == User) + “
”); Response.Write(“The impersonation token set by IIS is: “ + Request.LogonUserIdentity.Name + “
”); } } VB.NET Imports System Imports System.Security.Principal Imports System.Threading Partial Public Class _Default Inherits System.Web.UI.Page Protected Sub Page_Load(ByVal sender As Object, _ ByVal e As System.EventArgs) _ Handles Me.Load Response.Write(“The OS thread identity during BeginRequest is: “ & _ Context.Items(“OperatingSystem_ThreadIdentity_BeginRequest”) & _ “
”) Response.Write(“The managed thread identity during BeginRequest is: “ & _ Context.Items(“ManagedThread_ThreadIdentity_BeginRequest”) & _ “
”) Response.Write(“The managed thread identity during BeginRequest is “ & _ “a GenericPrincipal: “ & _ Context.Items(“ManagedThread_IsGenericPrincipal”) & _ “
”) Response.Write(“The user on the HttpContext during BeginRequest is: “ & _ Context.Items(“HttpContext_User_BeginRequest”) & _ “
”) Response.Write(“
”) Response.Write(“The OS thread identity when the page executes is: “ & _ WindowsIdentity.GetCurrent().Name & _ “
”) If String.IsNullOrEmpty(Thread.CurrentPrincipal.Identity.Name) Then Response.Write(“The managed thread identity when the “ & _ “page executes is:” & “[null or empty]” & _ “
”) Else Response.Write(“The managed thread identity when “ & _ “the page executes is:” & _ Thread.CurrentPrincipal.Identity.Name & “
”) End If Response.Write(“The managed thread identity is of type: “ & _ Thread.CurrentPrincipal.ToString() & “
”) If String.IsNullOrEmpty(User.Identity.Name) Then Response.Write(“The user on the HttpContext when “ & _ 90 Download at Boykma.Com Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model “the page executes is:” & “[null or empty]” & “
”) Else Response.Write(“The user on the HttpContext when the “ & _ “page executes is: “ & User.Identity.Name & “
”) End If Response.Write(“The user on the HttpContext is of type: “ & _ CType(User,Object).ToString() & “
”) Response.Write(“The user on the HttpContext and the “ & _ “thread principalpoint at the same object: “ & _ (Thread.CurrentPrincipal Is User) & “
”) Response.Write(“The impersonation token set by IIS is: “ & _ Request.LogonUserIdentity.Name & “
”) End Sub End Class The information is displayed running on an ASP.NET 3.5 application with the following characteristics: ❑❑ The site is running locally on the web server (that is, not on a UNC share). ❑❑ IIS has Anonymous Authentication and Windows Authentication modules enabled. ❑❑ ASP.NET is using the default mode of Windows for authentication. ❑❑ The element’s impersonate attribute is set to false. The page output is shown here: The OS thread identity during BeginRequest is: NT AUTHORITY\NETWORK SERVICE The managed thread identity during BeginRequest is: [null or empty] The managed thread identity during BeginRequest is a GenericPrincipal: True The user on the HttpContext during BeginRequest is: [null] -------------------------------------------------------------------------------The OS thread identity when the page executes is: NT AUTHORITY\NETWORK SERVICE The managed thread identity when the page executes is: [null or empty] The managed thread identity is of type: System.Security.Principal.WindowsPrincipal The user on the HttpContext when the page executes is: [null or empty] The user on the HttpContext is of type: System.Security.Principal.WindowsPrincipal The user on the HttpContext and the thread principal point at the same object: True The operating system thread identity makes sense because this is the identity of the underlying IIS 7.0 worker process. The ASP.NET runtime is not impersonating any identity, so the security context of the thread is not reset by ASP.NET. As mentioned earlier, during BeginRequest neither the HttpContext nor the Thread object have had any security information explicitly set by ASP.NET. The security information during page execution is a bit more interesting. The operating system thread identity has not changed. However, the IPrincipal associated with the current thread, and the IPrincipal associated with HttpContext is a reference to a WindowsPrincipal. Furthermore, the managed thread and HttpContext are referencing the same object instance. Clearly something occurred after Application_BeginRequest that caused a WindowsPrincipal to come into the picture. “Going back to the conditions under which the above code is running, it explains clearly what have been mentioned before that in the case of an application running under the IIS 7.0 Integrated mode, the native Download at Boykma.Com 91 Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model AnonymousAuthenticationModule is enabled, WindowsAuthenticationModule is enabled, and the ASP.NET application is configured to run with Windows authentication, the HttpContext.Current .User’s value will be set to a dummy instance of a WindowsPrincipal class having its Identity.Name property set to an empty string.” At this point, the important thing to keep in mind is that before the AuthenticateRequest event in the integrated request-processing pipeline occurs, neither the thread principal nor the User property of HttpContext should be relied on for identifying the security identity for the current request. The operating system identity, though, has been established. However, this identity can be affected by a number of factors, as you will see in the next section. Establishing the Operating System Thread Identity Both ASP.NET and IIS have a say in the identity of the underlying operating system thread that is used for request processing. By default, the identity is set to that of the IIS 7.0 worker process: NT AUTHORITY\ NETWORK SERVICE. However, developers and administrators have the option to change the default identity of the application pool by several ways, two of which follow: ❑❑ The default identity of the application pool is set in the ApplicationHost.config configuration file of the web server. ❑❑ A developer or administrator can open the ApplicationHost.config configuration file, find the configuration section located inside the configuration section group and then change the identityType attribute of the element. ❑❑ Another way of changing the default identity associated with the application pool is by visiting the IIS 7.0 Manager tool, clicking on the View Application Pools on the Actions menu on the right, selecting the application pool you want to change its identity, and finally clicking on the Advanced Settings. Figure 3-3 shows the Advanced Settings window used to configure advanced options for an application pool. In earlier versions of ASP.NET, determining the actual impersonation token set by the IIS core engine was difficult because the technique involved some rather esoteric code. However, it is easy to get a reference to it in ASP.NET 2.0 and 3.5. The following line of code gets a reference to the identity determined by IIS for the current request by its core engine: WindowsIdentity wi = Request.LogonUserIdentity; With this information, it is much simpler to see the IIS impersonation without the sometimes confusing effects of other authentication and configuration settings. For example, with the sample application used in the previous section (anonymous access allowed in IIS, Windows authentication enabled in ASP.NET, no impersonation), some of the security information for a page request is: The OS thread identity during BeginRequest is: NT AUTHORITY\NETWORK SERVICE The OS thread identity when the page executes is: NT AUTHORITY\NETWORK SERVICE The impersonation token set by IIS is: NT AUTHORITY\IUSR 92 Download at Boykma.Com Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model Figure 3-3 As you can see, the authenticated identity determined by IIS for the current request is the default identity of the anonymous authentication module. Recall that this application is running with the Anonymous​ AuthenticationModule enabled, the default identity of the aforementioned module was not changed and left with its default value. In addition, the application was not configured with a username or password hence the identity of the anonymous authentication module was used as the authenticated identity of the current request. Also notice that, even though the native WindowsAuthenticationModule was enabled, the AnonymousAuthenticationModule takes control over any other enabled native authentication module. The following table shows the various IIS security options and the resulting request authenticated identity set by IIS that can be accessed by ASP.NET: IIS Authentication Type Windows, Basic, Digest, or Certificate Mapping Anonymous Running on a UNC share with explicit credentials Impersonation Token Generated Token corresponding to the authenticated (or mapped) browser user. The default identity configured in IIS for anonymous authentication module. If not changed, it is by default IUSR built-in. The configured UNC identity. This identity is used regardless of the IIS authentication type. During the early stages of the request, enabling impersonation in an application running in the integrated mode has no real effect up until the request is authenticated by both IIS and ASP.NET. The setting of the impersonate attribute on the element will affect the operating system thread Download at Boykma.Com 93 Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model identity. Once the authentication stage is over and the page starts execution, ASP.NET will initialize the identity of the operating system thread based on a combination of the settings in the attribute and the request authenticated identity set by IIS core engine. When an ASP.NET application is configured to impersonate the authenticated user for the current request, the impersonation usually has an effect through all the stages of the request-processing pipeline, starting from the BeginRequest stage. However, with the IIS 7.0 Integrated mode, ASP.NET modules can now execute at early stages of the integrated request-processing pipeline. Therefore, impersonation is not available for ASP.NET applications running in the Integrated mode, only after the Authenticate​ Request stage. If your ASP.NET application makes use of impersonation in early stages of the requestprocessing pipeline, the IIS team at Microsoft recommends moving the application into the IIS 7.0 Classic mode. On the other hand, if you are not concerned with enabling impersonation for your application in the early stages of the request-processing pipeline, then the application operates according to the rules set by the IIS 7.0 Integrated mode. If the impersonate attribute of the element is set to true, then ASP.NET will change the operating system thread’s identity using the request authenticated identity set by IIS. However, if ASP.NET does not explicitly set the thread token, the operating system thread will run with the credentials configured for the worker process in IIS. Continuing with previous sample, if the following configuration change is made to the application, ASP.NET explicitly impersonates using the IIS impersonation token: The security information for the request changes to reflect the identity value of the impersonation token set by IIS. (At this point the sample application is not requiring IIS to authenticate the browser user): The OS thread identity during BeginRequest is: NT AUTHORITY\NETWORK SERVICE The OS thread identity when the page executes is: NT AUTHORITY\IUSR The impersonation token set by IIS is: NT AUTHORITY\IUSR An mentioned above, ASP.NET impersonation does not have a real effect on the identity of the operating system in the early stages of the integrated request processing pipeline; this is clear from the operating system thread identity inside the BeginRequest event. However, once the authentication stage is over, the impersonation effect is clear. ASP.NET sets the identity of the operating system thread to the impersonation token set by IIS core engine. Changing the settings in IIS to instead allow only native BasicAuthenticationModule causes IIS to set the impersonation token to the identity of the authenticated user. Again, if you look back at the section that talks about how IIS 7.0 determines the identity of the request, you will notice that the exact same thing is happening. Because ASP.NET impersonates this identity, the thread identity will reflect the impersonation token: The OS thread identity during BeginRequest is: NT AUTHORITY\NETWORK SERVICE The OS thread identity when the page executes is: bhaidar-PC\test The impersonation token set by IIS is: bhaidar-PC\test If the configuration for includes an explicit value for the username and password attributes then ASP.NET ignores the impersonation token set by IIS native modules, and ASP.NET instead 94 Download at Boykma.Com Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model explicitly sets the operating system’s thread token based on the credentials in the element. For example, if the sample application is switched back to allow Anonymous access in IIS and the configuration is changed to use the following: Then the security information reflects the application impersonation identity: The OS thread identity during BeginRequest is: NT AUTHORITY\NETWORK SERVICE The OS thread identity when the page executes is: bhaidar-PC\test The impersonation token set by IIS is: bhaidar-PC\bhaidar Notice that since Basic Authentication module is still the only native module configured in IIS, the user gets authenticated with basic authentication and IIS generates the impersonation token based on the username and password supplied by the user for the basic authentication. But since, ASP.NET application impersonates to a Windows account, it is clear that the operating system thread’s identity is set to the same username configured for the configuration section, inside the application’s web.config configuration file, when the impersonation is enabled. Prior to IIS 7.0, configuring application impersonation required that you manually edit the section in the application’s web.config configuration file. However, with IIS 7.0 you have a visual interface that allows you to edit the application impersonation, which is now known as ASP.NET impersonation, from inside the IIS 7.0 Manager tool. To use IIS 7.0 Manager to configure ASP.NET impersonation, locate the ASP.NET application in the list of hosted sites inside IIS 7.0. Figure 3-4 shows the IIS 7.0 Manager tool with an ASP.NET application selected. Figure 3-4 Download at Boykma.Com 95 Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model Double-click the selected Authentication icon and you will get a list of all authentication modules registered and installed by IIS 7.0. Figure 3-5 shows the available authentication modules for the ASP.NET application under study: Figure 3-5 In Figure 3-5 the ASP.NET Impersonation icon is selected. This icon has been added to allow developers and administrators to configure the configuration section through the IIS 7.0 Manager tool and is located explicitly within the authentication applet in IIS 7.0 Manager. Keep the ASP.NET Impersonation icon selected and click the Edit link from the Actions pane on the right-hand side. Figure 3-6 shows the dialog box that pops up when you click this link. Figure 3-6 This small dialog box is all you need to use to configure the ASP.NET impersonation, whether you want to configure client impersonation or application impersonation. As you can see, there are two main radio buttons: Specific user and Authenticated user. 96 Download at Boykma.Com Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model The Specific user option resembles what you already know as application impersonation. It is here that you can explicitly specify a username and password to impersonate the authentication identity of the request. Simply click on the Set button. Another dialog box appears that lets you enter a username, password, and confirmation password. On the other hand, the Authenticated user option simply impersonates the authentication identity of the request to the impersonation token set by IIS 7.0. This option resembles client impersonation. Throughout the previous samples, the sample application was running locally on the web server. If instead the sample application is placed on a UNC share configured with explicit UNC credentials, the only security identities used for the operating system thread are either the UNC credentials or the application impersonation credentials. This is due in part because IIS always set the impersonation token to the explicit UNC identity, regardless of whether or not the application in IIS is configured to require some type of authentication with the browser. When running the sample application on a UNC share without impersonation enabled, the security information looks like: The OS thread identity during BeginRequest is: bhaidar-PC\uncidentity The OS thread identity when the page executes is: bhaidar-PC\uncidentity The impersonation token set by IIS is: bhaidar-PC\uncidentity This highlights an important piece of ASP.NET security behavior. ASP.NET always ignores the true/ false state of the impersonate attribute when running on a UNC share. Instead, ASP.NET will impersonate the UNC identity. Running on a UNC share with client impersonation enabled (), the security information is exactly the same because of this behavior: The OS thread identity during BeginRequest is: bhaidar-PC\uncidentity The OS thread identity when the page executes is: bhaidar-PC\uncidentity The impersonation token set by IIS is: bhaidar-PC\uncidentity However, if application impersonation is configured for an application (that is, the username and password attributes of the element are set), then ASP.NET will ignore the impersonation token set by IIS and will instead set the operating system thread identity to the values specified in the element. Notice in the following output that the UNC identity is only available from the impersonation token set by IIS: The OS thread identity during BeginRequest is: bhaidar-PC\test The OS thread identity when the page executes is: bhaidar-PC\test The impersonation token set by IIS is: bhaidar-PC\uncidentity To summarize all this information, the following table lists the combinations of the impersonation token from IIS and operating system thread identities based on various configuration settings when running on IIS 7.0 integrated mode. Remember that client impersonation means , whereas application impersonation means an explicit username and password were configured in the element. In the following table, when running on a UNC share is yes, this means that the application in IIS has an explicit set of UNC credentials configured for accessing the share. As noted earlier, “officially” ASP.NET 3.5 is not supported running on a UNC share that uses pass-through authentication. Download at Boykma.Com 97 Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model On UNC Share No No No No No No Yes Yes Yes Yes Yes Yes IIS Authentication Anonymous allowed Anonymous allowed Anonymous allowed ASP.NET Impersonation None Client Application Authenticated access required Authenticated access required Authenticated access required None Client Application Anonymous allowed Anonymous allowed Anonymous allowed None Client Application Authenticated access required Authenticated access required Authenticated access required None Client Application OS Thread Identity NETWORK SERVICE IUSR IIS Impersonation Token IUSR IUSR The application impersonation credentials NETWORK SERVICE The credentials of the browser user The application impersonation credentials The configured UNC identity The configured UNC identity The application impersonation credentials The configured UNC identity The configured UNC identity The application impersonation credentials IUSR The credentials of the browser user The credentials of the browser user The credentials of the browser user The configured UNC identity The configured UNC identity The configured UNC identity The configured UNC identity The configured UNC identity The configured UNC identity The Unified Processing Pipeline In the new unified integrated mode of execution, both native and managed modules get the chance to subscribe to the same events during the request-processing pipeline. The different stages of execution are exposed to all the managed modules and hence, the new integrated mode can make heavy use of the modules developed by ASP.NET. 98 Download at Boykma.Com Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model A request in IIS 7.0 integrated mode passes through the same set of events as that of the ASP.NET pipeline events. As you know, the new integrated mode leverages ASP.NET to a framework to extend IIS 7.0 and hence, the ASP.NET pipeline will play a very important role from now on. Requests would pass through the same old ASP.NET pipeline events, both native and managed modules would subscribe to these events and hence, the expanded use of ASP.NET powers. And now for a brief interlude to review the processing pipeline in ASP.NET 3.5: A basic understanding of the pipeline is useful for knowing when authentication and authorization occur within the lifecycle of the integrated request-processing pipeline. Developers who have worked with the ASP.NET pipeline are usually familiar with the synchronous events that can be hooked. ASP.NET 3.5 expands on the original pipeline provided by ASP.NET 2.0 by adding three new events, which will be discussed shortly. The current ASP.NET 3.5 synchronous pipeline events are listed in the order that they occur as follows: 1. BeginRequest 2. AuthenticateRequest 3. PostAuthenticateRequest 4. AuthorizeRequest 5. PostAuthorizeRequest 6. ResolveRequestCache 7. PostResolveRequestCache 8. MapRequestHandler 9. PostMapRequestHandler 10. AcquireRequestState 11. PostAcquireRequestState 12. PreRequestHandlerExecute At this stage, the selected handler executes the current request. The most familiar handler is the Page handler. 13. PostRequestHandlerExecute 14. ReleaseRequestState 15. PostReleaseRequestState 16. UpdateRequestCache 17. PostUpdateRequestCache 18. LogRequest 19. PostLogRequest 20. EndRequest Download at Boykma.Com 99 Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model ASP.NET 3.5 adds three new stages to the unified integrated request-processing pipeline. These events are only used when the integrated mode is configured: 1. MapRequestHandler : At this stage a handler is selected based on the content file type exten- sion that is requested. Either a native module such as the StaticFileModule handler or a managed module such as PageHandlerFactory can be selected 2. LogRequest: Fires just after the PostUpdateRequestCache event. Even if an error occurs in the request processing, this even still fires. Both native and managed modules can subscribe to this event. 3. PostLogRequest: This event fires just after LogRequest event fires. The discussion will drill down to explain what happens during AuthenticateRequest, PostAuthenticateRequest, and AuthorizeRequest in more detail shortly. Suffice it to say that prior to the completion of AuthenticateRequest and PostAuthenticateRequest, only the operating system thread identity should be used. Other identities have not been completely initialized until these two events complete. For most developers, the operating system thread identity that is established prior to BeginRequest remains stable for the duration of the entire pipeline. Similarly, after authentication has occurred during AuthenticateRequest and PostAuthenticateRequest, the values of HttpContext.Current.User as well as Thread.CurrentPrincipal remain constant for the remainder of the pipeline. ASP.NET continues to support the ASP.NET 2.0’s asynchronous processing in the pipeline as well. After all, the core runtime of ASP.NET 3.5 is no different from ASP.NET 2.0, with some additional integrated features such as ASP.NET AJAX. For example, each of the synchronous events in the previous list also has a corresponding asynchronous event that developers can hook. Asynchronous pipeline processing makes it possible for developers to author long-running tasks without tying up ASP.NET worker threads. Instead, in ASP.NET 3.5 developers can start long running tasks in a way that quickly returns control to the current ASP.NET 3.5 worker thread. Then at a later point the ASP.NET runtime will be notified of the completion of the asynchronous work, and a worker thread is scheduled to continue running the pipeline again. Thread Identity and Asynchronous Pipeline Events Because of the support for asynchronous processing in ASP.NET 3.5, developers need to be cognizant of the security values available at different phases of asynchronous processing. In general, asynchronous pipeline events are handled in the following manner: 1. The developer subscribes to an asynchronous pipeline event in global.asax or with an HttpModule. Subscribing involves supplying a Begin and an End event handler for the asynchronous pipeline event. 2. ASP.NET runs the Begin event handler. The developer’s code within the Begin event handler kicks off an asynchronous task and returns the IAsyncResult handle to ASP.NET. 3. The asynchronous work actually occurs on a framework thread pool thread. This is a critical distinction, because when the actual work occurs, ASP.NET is not involved. No security information from the ASP.NET world will be auto-magically initialized. As a result, it is the responsibility of the developer to ensure that any required security identity information is explicitly passed to the asynchronous task. Furthermore, if the asynchronous task expects to be running 100 Download at Boykma.Com Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model under a specific identity, the task is responsible for impersonating prior to performing any work as well as reverting impersonation when the work is completed. 4. Once the asynchronous work is done, the thread pool thread will call back to ASP.NET to notify it that the work has completed. 5. As part of the callback processing, ASP.NET will call the developer’s End event handler. Nor- mally in the End event handler, the developer uses the IAsyncResult handle from step 2 to call EndInvoke and process the results. 6. ASP.NET starts up processing the page request again using a different ASP.NET worker thread. Before ASP.NET resumes running the request, it reinitializes the ASP.NET worker thread to ensure that the correct security context and security identities are being used. To make this a bit clearer, let’s walk through a variation of the identity sample used earlier. The asynchronous sample hooks the asynchronous version of PostAuthenticateRequest with an HttpModule. The reason behind subscribing to the PostAuthenticateRequest event is due to the breaking changes introduced by IIS 7.0 regarding impersonation. If you had to subscribe to the BeginRequest event, you would not have been able to see the effect of impersonation on the asynchronous pipeline events. The module is registered as follows: The module’s Init method is where the asynchronous event registration actually occurs. Notice that both a Begin and an End event handler are registered. C# using System; using System.Collections; using System.Data; using System.Configuration; using System.Security.Principal; using System.Threading; using System.Web; using System.Web.Security; public class AsyncEventsModule : IHttpModule { public AsyncEventsModule() { } #region IHttpModule Members public void Dispose() { throw new Exception(“The method or operation is not implemented.”); } public void Init(HttpApplication context) { Download at Boykma.Com 101 Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model context.AddOnPostAuthenticateRequestAsync( new BeginEventHandler(this.PostAuthenticateRequest_BeginEventHandler), new EndEventHandler(this. PostAuthenticateRequest_EndEventHandler) ); } #endregion //Implementations of being and end event handlers shown later } VB.NET Imports System Imports System.Collections Imports System.Data Imports System.Configuration Imports System.Security.Principal Imports System.Threading Imports System.Web Imports System.Web.Security Imports System.Web.UI Imports System.Web.UI.WebControls Imports System.Web.UI.WebControls.WebParts Imports System.Web.UI.HtmlControls Public Class AsyncEventsModule Implements IHttpModule Public Sub New() End Sub #Region “IHttpModule Members” Public Sub Dispose() Implements IHttpModule.Dispose End Sub Public Sub Init(ByVal context As HttpApplication) Implements IHttpModule.Init context.AddOnPostAuthenticateRequestAsync( _ New BeginEventHandler( _ AddressOf Me.PostAuthenticateRequest_BeginEventHandler), _ New EndEventHandler( _ AddressOf Me.PostAuthenticateRequest_EndEventHandler)) End Sub #End Region ‘Implementations of being and end event handlers shown later End Class Within the same ASP.NET application, there is a class called Sleep that will sleep for one second when one of its methods is called. The Sleep class simulates a class that would perform some type of lengthy work that is best executed in the background. The constructor for the Sleep class accepts a reference to an IDictionary. This will be used to initialize the Sleep class with a reference to the HttpContext’s Items collection. Using the Items collection, an instance of the Sleep class can log the operating system thread identity, both during asynchronous execution and after completion of asynchronous processing. 102 Download at Boykma.Com Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model C# using System.Collections; using System.Security.Principal; using System.Threading; … public class Sleep { private IDictionary state; public Sleep(IDictionary appState) { state = appState; } public void DoWork() { state[“AsyncWorkerClass_OperatingSystemThreadIdentity”] = WindowsIdentity.GetCurrent().Name; Thread.Sleep(1000); } public void StoreAsyncEndID() { state[“AsyncWorkerClass_EndEvent_OperatingSystemThreadIdentity”] = WindowsIdentity.GetCurrent().Name; } } VB.NET Imports System Imports System.Collections Imports System.Security.Principal Imports System.Threading … Public Class Sleep Private state As IDictionary Private aspnetThreadToken As IntPtr Public Sub New(ByVal appState As IDictionary, ByVal token As IntPtr) state = appState End Sub Public Sub DoWork() state(“AsyncWorkerClass_OperatingSystemThreadIdentity”) = _ WindowsIdentity.GetCurrent().Name Thread.Sleep(1000) End Sub Public Sub StoreAsyncEndID() state(“AsyncWorkerClass_EndEvent_OperatingSystemThreadIdentity”) _ = WindowsIdentity.GetCurrent().Name End Sub End Class Download at Boykma.Com 103 Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model The Begin event handler for PostAuthenticateRequest will use a delegate to trigger an asynchronous call to the DoWork method. The module defines a delegate that is used to wrap the DoWork method on the Sleep class as follows: C# public delegate void AsyncSleepDelegate(); VB.NET Public Delegate Sub AsyncSleepDelegate() For simplicity, the Begin and End pipeline event handlers are also implemented as part of the same HttpModule. The Begin event handler (which follows), first obtains a reference to the HttpContext associated with the current request by casting the sender parameter to an instance of HttpApplication. Using the context, the module stores the operating system thread identity. Then the module creates an instance of the class that will perform the actual asynchronous work. After wrapping the DoWork method with an AsyncSleepDelegate, the module calls BeginInvoke. The code passes the AsyncCallback reference supplied by ASP.NET as one of the parameters to BeginInvoke. This is necessary because it is the ASP.NET runtime that is called back by the .NET Framework thread pool thread carrying out the asynchronous work. Without hooking up the callback, there would be no way for the flow of execution to return back to ASP.NET after an asynchronous piece of work was completed. The second parameter passed to BeginInvoke is a reference to the very AsyncSleepDelegate being called. As a result, the delegate reference will be available when asynchronous processing is completed and EndInvoke is called on the delegate. The return value from any call made to a BeginInvoke method is a reference to an IAsyncResult. The BeginInvoke method is auto-generated by the .NET Framework to support asynchronous method calls without developers needing to explicitly author asynchronous class definitions. Returning an IAsyncResult allows ASP.NET to pass the reference back to the developer’s End event later on when asynchronous processing is complete. C# private IAsyncResult PostAuthenticateRequest_BeginEventHandler( object sender, EventArgs e, AsyncCallback cb, object extraData) { HttpApplication a = (HttpApplication)sender; a.Context.Items[“PostAuthenticateRequestAsync_OperatingSystemThreadID”] = WindowsIdentity.GetCurrent().Name; Sleep s = new Sleep(a.Context.Items); AsyncSleepDelegate asd = new AsyncSleepDelegate(s.DoWork); IAsyncResult ar = asd.BeginInvoke(cb, asd); return ar; } VB.NET Private Function PostAuthenticateRequest_BeginEventHandler( _ ByVal sender As Object, _ ByVal e As EventArgs, _ ByVal cb As AsyncCallback, _ 104 Download at Boykma.Com Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model ByVal extraData As Object) As IAsyncResult Dim a As HttpApplication = CType(sender, HttpApplication) a.Context.Items(“PostAuthenticateRequestAsync_OperatingSystemThreadID”) _ = WindowsIdentity.GetCurrent().Name ‘the Sleep class is now constructed with: Dim s As New Sleep(a.Context.Items, WindowsIdentity.GetCurrent().Token) Dim asd As New AsyncSleepDelegate(AddressOf s.DoWork) Dim ar As IAsyncResult = asd.BeginInvoke(cb, asd) Return ar End Function When asynchronous work has completed, the .NET Framework calls back to ASP.NET using the callback reference that was supplied earlier to the BeginInvoke call. As part of the callback processing, ASP.NET calls the End event (which follows) that was registered, passing it the IAsyncResult that was returned from the BeginInvoke call. This allows the End event to cast the AsyncState property available from IAsyncResult back to a reference to the AsyncSleepDelegate. The End event can now call EndInvoke against the AsyncSleepDelegate to gather the results of the asynchronous processing. In the sample application, there is no return value, but in practice any asynchronous processing would probably return a reference to a query or some other set of results. Because the End event now has a reference to the AsyncSleepDelegate, it can use the Target property of the delegate to get back to the original instance of Sleep that was used. The End event then logs the current operating system thread identity as it exists during the End event using the StoreAsyncEndID method on the Sleep instance. At this point, having the Sleep instance log the thread identity is acceptable because this method call is synchronous and thus executes on the same thread running the End event handler. C# private void PostAuthenticateRequest_EndEventHandler(IAsyncResult ar) { AsyncSleepDelegate asd = (AsyncSleepDelegate)ar.AsyncState; asd.EndInvoke(ar); Sleep s = (Sleep)asd.Target; s.StoreAsyncEndID(); } VB.NET Private Sub PostAuthenticateRequest_EndEventHandler(ByVal ar As IAsyncResult) Dim asd As AsyncSleepDelegate = CType(ar.AsyncState, AsyncSleepDelegate) asd.EndInvoke(ar) Dim s As Sleep = CType(asd.Target, Sleep) s.StoreAsyncEndID() End Sub Download at Boykma.Com 105 Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model You can run the sample with a variety of different settings for in the web.config configuration file, as well as the directory security settings in IIS. Using the sample code earlier, the following extra lines of code show the asynchronous identity information. C# Response.Write(“The OS thread identity during “ + “PostAuthenticateRequest_BeginEventHandler is: “ + Context.Items[“PostAuthenticateRequestAsync_OperatingSystemThreadID”] + “
”); Response.Write(“The OS thread identity during “ + “the actual async work is: “ + Context.Items[“AsyncWorkerClass_OperatingSystemThreadIdentity”] + “
”); Response.Write(“The OS thread identity during “ + “PostAuthenticateRequest_EndEventHandler is: “ + Context.Items[“AsyncWorkerClass_EndEvent_OperatingSystemThreadIdentity”] + “
”); VB.NET Response.Write(“The OS thread identity during “ & _ “PostAuthenticateRequest_BeginEventHandler is: “ & _ Context.Items(“PostAuthenticateAsync_OperatingSystemThreadID”) & _ “
”) Response.Write(“The OS thread identity during “ & _ “the actual async work is: “ & _ Context.Items(“AsyncWorkerClass_OperatingSystemThreadIdentity”) & _ “
”) Response.Write(“The OS thread identity during “ & _ “PostAuthenticateRequest_EndEventHandler is: “ & _ Context.Items(“AsyncWorkerClass_EndEvent_OperatingSystemThreadIdentity”) & _ “
”) The following results show the identity information with Anonymous access allowed in IIS and the configured for application impersonation: The OS thread identity during BeginRequest is: NT AUTHORITY\NETWORK SERVICE The OS thread identity during PostAuthenticateRequest_BeginEventHandler is: bhaidar-PC\test The OS thread identity during the actual async work is: NT AUTHORITY\NETWORK SERVICE The OS thread identity during PostAuthenticateRequest_EndEventHandler is: NT AUTHORITY\NETWORK SERVICE The OS thread identity when the page executes is: bhaidar-PC\test The impersonation token from IIS is: NT AUTHORITY\IUSR As you can see, the Begin event handler uses the default application pool identity NT AUTHORITY\ NETWORK SERVICE instead of the application impersonation account. As previously mentioned above, 106 Download at Boykma.Com Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model when an ASP.NET application is running inside the IIS 7.0 Integrated mode, the application impersonation has no effect before the AuthenticateRequest stage, where the request would have been authenticated by both IIS 7.0 and ASP.NET. However, remember from above that the code subscribed to the PostAuthenticateRequest. At this stage the ASP.NET application impersonation has an effect on the operating system thread, which is clear from the output above where the operating system thread in the PostAuthenticateRequest represents that of the ASP.NET application impersonation. However, during the asynchronous work in the Sleep instance, a thread from the .NET Framework thread pool was used. Because the application is running in an IIS 7.0 worker process, the default identity for any operating system threads is the identity of the worker process. In this case, the worker process is using the default identity of NT AUTHORITY\NETWORK SERVICE. You can clearly see that the application impersonation has no effect at all here and is regardless of the fact that the request is operating in the PostAuthenticateRequest stage (where application impersonation takes effect). The End event handler also executes on a thread pool thread. As a result, the operating system thread identity is also NT AUTHORITY\NETWORK SERVICE. Do not be mixed up with the fact that the Begin and End events were registered during the PostAuthenticateRequest stage. The asynchronous work is done on a separate thread that the .NET Framework has chosen from the thread pool, and hence the application authentication has no effect on those threads located in the thread pool. Because the work that occurs in the End event handler is usually limited to just retrieving the results from the asynchronous call, the identity of the thread at this point should not be an issue. Note that just from an architectural perspective, you should not be performing any “heavy” processing at this point. The general assumption is that the End event handler is used for any last pieces of work after asynchronous processing is completed. This highlights the fact that if a developer depends on the thread identity during asynchronous work (for example, a call is made to SQL Server using integrated security), the developer is responsible for impersonating and reverting identities during the asynchronous call, regardless of whether the asynchronous work is performed before or after PostAuthenticateRequest event and whether application impersonation is enabled or not. Because you own the work of safely manipulating the thread identity at this point, you may need to carefully wrap all work in a try/finally block to ensure that the thread pool’s thread identity is always reset to its original state. Although some tricks can be used to marshal an appropriate security token over to an asynchronous worker class, performing work that requires specific credentials will always be a bit complicated. For example, the sample intentionally used application impersonation to show that the application impersonation identity is not available during asynchronous processing. If an application required this identity to perform a piece of asynchronous work, you would need to first get a copy of the operating system thread token in the Begin event (there is a Token property on WindowsIdentity), and then pass the token to the asynchronous worker class. If the Sleep class is modified to accept a token in its constructor, it can impersonate the necessary identity in the DoWork method when asynchronous work is performed: C# //the Sleep class is now constructed with: Sleep s = new Sleep(a.Context.Items,WindowsIdentity.GetCurrent().Token); public class Sleep { private IDictionary state; Download at Boykma.Com 107 Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model private IntPtr aspnetThreadToken; public Sleep(IDictionary appState, IntPtr token) { state = appState; aspnetThreadToken = token; } public void DoWork() { WindowsIdentity wi = new WindowsIdentity(aspnetThreadToken); WindowsImpersonationContext wic = null; try { wic = wi.Impersonate(); state[“AsyncWorkerClass_OperatingSystemThreadIdentity”] = WindowsIdentity.GetCurrent().Name; Thread.Sleep(1000); } finally { if (wic != null) wic.Undo(); } } //StoreAsyncEndID snipped for brevity } VB.NET ‘the Sleep class is now constructed with: Dim s As New Sleep(a.Context.Items, WindowsIdentity.GetCurrent().Token) Public Class Sleep Private state As IDictionary Private aspnetThreadToken As IntPtr Public Sub New(ByVal appState As IDictionary, ByVal token As IntPtr) state = appState aspnetThreadToken = token End Sub Public Sub DoWork() Dim wi As WindowsIdentity = Nothing If aspnetThreadToken <> IntPtr.Zero Then wi = New WindowsIdentity(aspnetThreadToken) End If Dim wic As WindowsImpersonationContext = Nothing Try 108 Download at Boykma.Com Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model If aspnetThreadToken <> IntPtr.Zero Then wic = wi.Impersonate() End If state(“AsyncWorkerClass_OperatingSystemThreadIdentity”) _ = WindowsIdentity.GetCurrent().Name Thread.Sleep(1000) Finally If wic IsNot Nothing Then wic.Undo() End If End Try End Sub ‘StoreAsyncEndID snipped for brevity End Class The result of impersonating the identity during the asynchronous work shows that now the application impersonation identity is available: The OS thread identity during BeginRequest_BeginEventHandler is: NT AUTHORITY\IUSR The OS thread identity during the actual async work is: bhaidar-PC\testThe OS thread identity during BeginRequest_EndEventHandler is: NT AUTHORITY\NETWORK SERVICE Once again it is important to mention that the AsyncEventsModule has been updated to register for the AddOnPostAuthenticateRequestAsync since only at this event can you see the effect of impersonating an ASP.NET application. Before the AuthenticateReqesut event occurs, the impersonation will have no effect on the operating system thread. Moreover, if you plan to see the effect of the ASP.NET application impersonation during the processing and execution of asynchronous work, you should also impersonate to retrieve the operating system thread identity, which in this case is also an impersonated identity due to application impersonation and the location where the asynchronous work is registered (the PostAuthenticateRequest stage). Overall, the moral of the story here is that when planning for asynchronous pipeline events, the question of the identity needed to carry out the background work needs to be considered early on. If using the worker process identity is not an option, for simplicity using a fixed set of identity information that can be loaded from configuration or encapsulated in a worker class may be a better choice than trying to “hop” the ASP.NET thread’s security identity over the wall to the asynchronous worker class. Although the modifications shown earlier were pretty simple, the actual identity that is used will vary depending on IIS and ASP.NET security settings. Trying to debug why a background task is failing will be much more difficult if the task depends on an identity that can be easily changed with a few misconfigurations. Although it is not shown here, if the security information required by your asynchronous task is instead just the IPrincipal from either HttpContext.Current.User or Thread.CurrentPrincipal, you can pass the IPrincipal reference to your asynchronous worker class. In the case of HttpContext​ .Current.User, it is even easier because you can just pass an HttpContext reference to your worker class (the sample passed the Items collection from the current HttpContext). You may need the IPrincipal, for example, if you pass user information to your middle tier for authorization or auditing purposes. Download at Boykma.Com 109 Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model Also, note that in some cases the value of Thread.CurrentPrincipal may appear to be retained across the main ASP.NET request, and your asynchronous task. However, this behavior should not be relied on, because it is entirely dependent on which managed thread is selected from the framework’s thread pool to execute asynchronous tasks. One last piece of information about managing security for asynchronous tasks is in order. The sample you looked at used a separate class to carry out the asynchronous work. However, a number of .NET Framework classes provide methods that return an IAsyncResult reference. For example, both the System.IO.FileStream and the System.Data.SqlClient.SqlCommand classes support asynchronous reads. As another example, the System.Net.HttpWebRequest class also supports making asynchronous requests to HTTP endpoints. In cases like these, you need to look at the class signatures and determine if they have any built-in support for passing a security identity along to their asynchronous processing. In the case of System.Net.HttpWebRequest, there is a Credentials property that you can explicitly set. When the HttpWebRequest class asynchronously makes a request, it will use the security information that you set in the Credentials property. A similar ability to automatically pass along the correct credentials exists when using the SqlCommand and SqlConnection classes. AuthenticateRequest The AuthenticateRequest event is the point in the unified HTTP pipeline where both IIS and ASP.NET participate in authenticating the request. It is at this stage the IIS 7.0 core engine detects the configured authentication modules and executes them. The process that IIS follows to authenticate a request has been discussed in details above. Therefore the focus here will be on the managed authentication side of the authentication process done by ASP.NET. It is this one that gives developers the opportunity to write code to examine the current security information for a request and based upon it, create an IPrincipal implementation and attach it to the current ASP.NET request. The end result of AuthenticateRequest is that both the managed thread’s identity (available from Thread.CurrentPrincipal) and the User property of the current HttpContext will be initialized to an IPrincipal that can be used by downstream code. Be default, ASP.NET ships with a number of HttpModules that hook the AuthenticateRequest event. You can see this list (and modify it) in the root web.config configuration file that is available in the following location: %windir%\Microsoft.NET\Framework\v2.0.50727\CONFIG The web.config configuration file in the framework’s CONFIG directory is a concept that was introduced with ASP.NET 2.0. The development teams at Microsoft decided to separate web-specific configuration out of the machine.config configuration file to speed up load times for non-web applications. As a result, non-ASP.NET applications do not have to chug through configuration sections for features unsupported outside of a web environment. Looking at the configuration element in the root web.config configuration file, the following entries are for modules that hook AuthenticateRequest: Of the three default modules, we will only take a closer look at the WindowsAuthenticationModule and FormsAuthenticationModule. The PassportAuthentication is not supported anymore on Windows Vista and Windows Server 2008. WindowsAuthenticationModule When the ASP.NET application is running under IIS 7.0 classic mode, the managed Windows AuthenticationModule is the only authentication module that depends on impersonation token set by IIS. Its purpose is to construct a WindowsPrincipal based on the authenticated identity set by IIS when a web.config configuration file contains the setting . The resultant WindowsPrincipal is set as the value of the User property for the current HttpContext. If a different authentication mode has been configured, the WindowsAuthenticationModule immediately returns whenever it is called during the AuthenticateRequest event. Note that the module does not look at or use the security identity of the underlying operating system thread when creating a WindowsPrincipal. As a result, the settings in the element have no effect on the output from the WindowsAuthenticationModule. On the other hand, when the ASP.NET application is running under IIS 7.0 integrated mode, the managed WindowsAuthenticationModule behaves differently. It simply disregards the impersonation token set by IIS 7.0 and focuses on the HttpContext.Current.User property. In case a native authentication module was executed and successful, this means there is a valid and authenticated user. As mentioned above, there is an integrated request processing pipeline mechanism that sets the HttpContext.​Current.User property to the value of the native User principal. The managed WindowsAuthenticationModule simply casts the value in the HttpContext.Current.User property into a valid WindowsPrincipal instance. However, if the native AnonymousAuthenticationModule is enabled, the HttpContext.Current.User property will be null and not set by the integrated request processing pipeline, since the native Anonymous​ AuthenticationModule runs after all the authentication modules configured in IIS 7.0. Therefore, if the native AnonymousAuthenticationModule is enabled and the application is running under the IIS 7.0 integrated mode, the managed WindowsAuthenticationModule has no use and can be easily removed without causing any problem to the application. The name of the module WindowsAuthenticationModule is a little misleading because in reality this module does not actually authenticate a user. Authentication usually implies some kind of challenge (username and password), a response and a resultant representation of the success or failure of the challenge/response. However, this module is not involved in any challenge/response sequence. Instead, all this occurs up front in IIS. If IIS is configured to require some type of authenticated access to an application (Windows using NTLM or Kerberos, Basic, Digest, or Certificate Mapping), then it is IIS that challenges the browser for credentials according to the enabled authentication types. If the response succeeds (and in some cases the response involves multiple network round trips to complete all of the security negotiations), then it is IIS that creates the data that represents a successfully authenticated user by doing all of the following: ❑❑ Generating the impersonation token that represents the authenticated user and making this identity available to ASP.NET. Download at Boykma.Com 111 Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model ❑❑ Setting the values of the LOGON_USER and AUTH_TYPE server variables to reflect the authenticated user and the authentication type that was used. ❑❑ If the ASP.NET application is running under the IIS 7.0 integrated mode and a native authentication module like Basic or Windows authentication is enabled, the integrated request processing pipeline uses a mechanism to set the HttpContext.Current.User property to the value of the native User principal. In IIS 7.0 classic mode, WindowsAuthenticationModule just consumes the results of the security negotiations with IIS and makes the results of these negotiations available as a WindowsPrincipal. When the LOGON_USER and AUTH_TYPE server variables are empty (that is, no authentication challenge took place at the IIS 7.0 native authentication modules (the managed WindowsAuthenticationModule initializes the HttpContext.Current.User property to an anonymous identity that is the value of WindowsIdentity.GetAnonymous(). This anonymous identity has the following characteristics: ❑❑ The value of Name is the empty string. ❑❑ The value of AuthenticationType is the empty string. ❑❑ IsAnonymous is set to true. ❑❑ IsAuthenticated is set to false. In other words, the managed WindowsAuthenticationModule inspects the LOGON_USER and AUTH_TYPE server variables for the current request. If those variables are empty, no authentication challenge took place at the IIS 7.0 level. Consequently, it constructs a WindowsPrincipal containing an anonymous WindowsIdentity, which determines that no browser user was authenticated for the current request and simply ignores the impersonation token set by IIS. If the server variables were not empty, the managed WindowsAuthenticationModule constructs a new WindowsPrincipal instance and assigns it to the HttpContext.Current.User property based on the server variables mentioned at the beginning of this paragraph. In addition, the Identity property on the User property is initialized to a new instance of the WindowsIdentity class. On the other hand, when an application is running in the IIS 7.0 integrated mode, the managed Windows​ AuthenticationModule has a minimal job to do. It simply does some internal initialization based on the HttpContext.Current.User property’s value that was originally passed a WindowsPrincipal instance by an integrated request processing pipeline mechanism based on the native User principal. It was mentioned above that when the native AnonymousAuthenticationModule is enabled and the application is configured for Windows authentication and running under the integrated mode, the managed WindowsAuthenticationModule will not fire its Authenticate event and will perform only internal tasks. The reason behind this is that when the native AnonymousAuthenticationModule is enabled, regardless of the native authentication module configured, no authentication process takes place and hence regardless what native authentication modules have been configured, the HttpContext.​Current​ .User property is empty. Internally the managed WindowsAuthenticationModule checks if the Identity property on the HttpContext.Current.User is null in C# or Nothing in VB.NET the Authenticate event never fires. 112 Download at Boykma.Com Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model To illustrate how the HttpContext.Current.User property is determined when the native AnonymousAuthenticationModule is enabled, let’s go back and check the output log that was generated by the previous sample code shown above: The managed thread identity when the page executes is: [null or empty] The managed thread identity is of type: System.Security.Principal.WindowsPrincipal The user on the HttpContext when the page executes is: [null or empty] The user on the HttpContext is of type: System.Security.Principal.WindowsPrincipal The user on the HttpContext and the thread principal point at the same object: True Now you know why the IPrincipal attached to both the context and the thread is a WindowsPrincipal with a username of empty string. The native AnonymousAuthenticationModule is enabled, meaning that no authentication challenge takes place and hence the integrated request processing pipeline does not set any value to the HttpContext.Curent.User property. The application is configured by default with Windows authentication and since the native AnonymousAuthenticationModule runs as the last authentication module at the AuthenticateRequest stage, it finds out that the User property is still invalid, and it informs the integrated request processing pipeline to instantiate the HttpContext.Current.User property to an anonymous WindowsPrincipal instance that has an anonymous Identity property of type WindowsIdentity. On the other hand, if an authenticated browser user is detected (i.e., LOGON_USER and AUTH_TYPE are not empty strings) and the application runs under the IIS 7.0 classic mode, WindowsAuthenticationModule looks at the impersonation token set by IIS and creates a WindowsIdentity with the token. Regardless of whether the application is running in either the integrated or classic mode, after the module creates a WindowsIdentity (either an authenticated identity in both classic and integrated mode or an anonymous identity in the classic mode), it raises the Authenticate event. If the event is fired, a developer can choose to hook the Authenticate event from WindowsAuthenticationModule. The WindowsIdentity that the module created is passed as part of the event argument of type Windows​ AuthenticationEventArgs. A developer can choose to create a custom principal in their event handler by setting the User property on the WindowsAuthenticationEventArgs event argument. The thing that is a little weird about this event is that a developer can actually do some pretty strange things with it. For example: ❑❑ A developer could technically ignore the WindowsIdentity supplied by the module and create a custom IIdentity wrapped in a custom IPrincipal implementation and then set this custom IPrincipal on the WindowsAuthenticationEventArgs User property. ❑❑ Alternatively, a developer could obtain a completely different WindowsIdentity (in essence ignoring the request authenticated identity set by IIS) and then wrap it in a WindowsPrincipal and set it on the event argument’s User property. In general, though, there is not a compelling usage of the Authenticate event for most applications. The Authenticate event was originally placed on this module (and others) to make it easier for developers to figure out how to attach custom IPrincipal implementations to an HttpContext without needing to create an HttpModule or hook events in global.asax. Architecturally, though, it makes more sense to just let WindowsAuthenticationModule carry out its work, and not hook the Authenticate event. If a web application needs to implement a custom authentication mechanism, it should use a custom HttpModule that itself hooks the AuthenticateRequest pipeline event. Both ASP.NET 2.0 and Download at Boykma.Com 113 Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model ASP.NET 3.5 make this approach even easier because you can author the module with a class file inside of the App_Code directory and just reference the type (without all of the other assembly identification information) inside of the configuration section of the web.config configuration file when the application is running under IIS 7.0 Classic mode, or inside of the configuration section of the web.config configuration file when the application is running under IIS 7.0 Integrated mode. Once the Authenticate event returns, WindowsAuthenticationModule looks at the User property on the WindowsAuthenticationEventArgs that was passed to the event. If an IPrincipal was set, the module sets the value of HttpContext.Current.User to the IPrincipal reference. If the User property on the event argument is null, though (the normal case), the module wraps the WindowsIdentity it determined earlier (either an anonymous WindowsIdentity, or a WindowsIdentity corresponding to the IIS impersonation token) in a WindowsPrincipal, and sets this principal on HttpContext​ .Current.User. Using the sample application shown earlier in the chapter, look at a few variations of IIS security settings and UNC locations while using Windows authentication. Earlier, you saw the results of running with AnonymousAuthenticationModule enabled in IIS for a local web application. If instead, some type of authenticated access is required in IIS (Windows, Digest, Basic, or Certificate Mapping), the output changes to reflect the authenticated browser user. The OS thread identity when the page executes is: bhaidar-PC\test The managed thread identity when the page executes is: bhaidar-PC\bhaidar The managed thread identity is of type: System.Security.Principal.WindowsPrincipal The user on the HttpContext when the page executes is: bhaidar-PC\bhaidar The user on the HttpContext is of type: System.Security.Principal.WindowsPrincipal Regardless of whether impersonation is in effect (in this case, I enabled application impersonation), the value of Thread.CurrentPrincipal and HttpContext.Current.User will always reflect the authenticated browser user (and hence the request authenticated identity set by IIS) when some type of browser authentication is required. If the application is running on a UNC share using explicit UNC credentials, continues to function properly with an exception, which is that when authentication is enabled for an application, it will ignore the impersonation token generated by IIS that is based on the UNC share credentials and simply uses the credentials of the authenticated user. Remember that in earlier UNC examples you saw that the impersonation token from IIS always reflected the explicit UNC credentials. Because WindowsAuthentication​ Module creates a WindowsPrincipal that is either an anonymous identity, or an identity matching the impersonation token from IIS, this means that even in the UNC case there will only ever be one of two possible WindowsPrincipal objects attached to the thread and the context: an anonymous Windows​ Identity, or an identity matching the authenticated credentials negotiated by the IIS when authentication was performed. The following output is for the same application using application impersonation and running on a UNC share with anonymous access allowed: The OS thread identity when the page executes is: bhaidar-PC\test The managed thread identity when the page executes is: [null or empty] The managed thread identity is of type: System.Security.Principal.WindowsPrincipal The user on the HttpContext when the page executes is: [null or empty] The user on the HttpContext is of type: System.Security.Principal.WindowsPrincipal 114 Download at Boykma.Com Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model When authenticated access to the application is required, the UNC identity will not have any effect on the thread and the context identities. Instead, the application impersonation identity will take control. The account bhaidar-PC\test was used as the application impersonation account throughout this chapter. The OS thread identity when the page executes is: bhaidar-PC\test The managed thread identity when the page executes is: bhaidar-PC\bhaidar The managed thread identity is of type: System.Security.Principal.WindowsPrincipal The user on the HttpContext when the page executes is: bhaidar-PC\bhaidar The user on the HttpContext is of type: System.Security.Principal.WindowsPrincipal The following table summarizes the type of WindowsIdentity that is set on the HttpContext for various settings: Running on a UNC Share? No No Yes Yes Authenticated Access Required in IIS? No Yes No Yes WindowsIdentity Set on the HttpContext A WindowsIdentity corresponding to an anonymous user. WindowsIdentity.GetAnonymous() A WindowsIdentity corresponding to the authenticated browser user The value of WindowsIdentity.GetAnonymous() A WindowsIdentity corresponding to the authenticated browser user FormsAuthenticationModule To start with, FormsAuthenticationModule is now registered on IIS 7.0 once the ASP.NET feature is enabled on the web server. Hence, this module can now be enabled for an application from the IIS 7.0 Manager tool and of course from inside the configuration section group of the application’s web.config configuration file. In addition, taking advantage of the IIS 7.0 and ASP.NET integrated mode of execution, FormsAuthenticationModule can be used to authenticate requests for non-ASP.NET resources. This is because when it is time to authenticate a request, IIS 7.0 and ASP.NET would be executing the enabled native and managed authentication modules at the same authentication stage in the unified request-processing pipeline. FormsAuthenticationModule inspects the cookies and the URL of the incoming request, looking for a forms authentication ticket (an encrypted representation of a FormsAuthenticationTicket instance). If the authentication mode is set to forms , the module will use a valid ticket to create a GenericPrincipal containing a FormsIdentity, and set the principal on HttpContext.Current.User. If a different authentication mode has been configured, then the module immediately exits during the AuthenticateRequest event. Before the module attempts to extract a forms authentication ticket, it raises an Authenticate event. This event is similar in behavior to the Authenticate event raised by WindowsAuthenticationModule. Developers can choose to hook the Authenticate event on the FormsAuthenticationModule and supply a custom IPrincipal implementation by setting the User property on the FormsAuthentication​ EventArgs parameter that is passed to the event. After the event fires, if an IPrincipal was set on the event argument, FormsAuthenticationModule sets the value of HttpContext.Current.User to the same value, and then exits. Download at Boykma.Com 115 Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model In forms authentication, the Authenticate event is a bit more useful, because conceptually “forms” authentication implies some type of logon form that gathers credentials from a user. Hooking the Authenticate event can be useful if developers programmatically create a FormsAuthentication​ Ticket, but then need to manage how the ticket is issued and processed on each subsequent request. As with the WindowsAuthenticationModule, the Authenticate event can be used as just a convenient way to author a completely custom authentication scheme without needing to author and then register an HttpModule. If you do not hook the event, the normal processing of FormsAuthenticationModule occurs. In Chapter 6, on forms authentication, you learn more about the options available for handling forms authentication tickets. Briefly though, the sequence of steps the module goes through to arrive at a FormsIdentity are: 1. The module first gets the encrypted ticket that may have been sent as part of the request. The ticket could be in a cookie, in a custom HTTP header, in a query-string variable, or in a posted form variable. 2. After the module has the ticket, it attempts to decrypt it. If decryption succeeds, the module now has a reference to an instance of FormAuthenticationTicket. Some other validations occur, including confirming that the ticket has not expired, and that if SSL is required for cookie-based tickets that the current request is running under SSL. 3. If decryption or any of the subsequent validations fail, then the ticket is invalid and the Forms​ AuthenticationModule explicitly clears the ticket by either issuing an outdated cookie or clearing the cookieless representation from the HTTP_ASPFILTERSESSIONID header. At this point the module exits, which means no IPrincipal is created or attached to the context. 4. If a valid ticket was found but the ticket was in a query-string variable or was part of a posted form variable, the module will transfer the ticket into either a cookie or the cookieless representation of a forms authentication ticket. A side effect of this is that the module will trigger a redirect if transferring the ticket to a cookieless representation. 5. The module then creates an instance of a GenericPrincipal. Because forms authentication has no concept of roles and requires no custom properties or methods on the principal, it uses a GenericPrincipal. The custom representation for forms authentication is the FormsIdentity class. By this point, the module has a reference to a FormsAuthenticationTicket instance as a side effect of the earlier decryption step. It constructs a FormsIdentity, passing in the Forms​ AuthenticationTicket reference to the constructor. The FormsIdentity instance is then used to construct a GenericPrincipal. 6. GenericPrincipal is set as the value of the User property on the current HttpContext. 7. The module may update the expiration date for the ticket if sliding expirations have been enabled for forms authentication. As with step 4, when working with cookieless tickets, automatically updating the expiration date will trigger a redirect. 8. FormsAuthenticationModule sets the public SkipAuthorization property on the cur- rent HttpContext. Note that even though the module sets this property, it does not actually use it. Instead downstream managed authorization modules can inspect this property when authorizing a request. The module will set the property to true if either the configured forms authentication login page is being requested (it would not make any sense to deny access to 116 Download at Boykma.Com Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model the application’s login page), or if the current request is for the ASP.NET assembly resource handler (AssemblyResourceLoader-Integrated), which is configured in the configuration section of the ApplicationHost.config configuration file. The reason for the extra check for webresource.axd is that it is possible to remove the handler definition from configuration, in which case ASP.NET no longer considers webresource.axd to be a special request that should skip authorization. Unlike WindowsAuthenticationModule, FormsAuthenticationModule sets up security information that is divorced from any information about the operating system thread identity. In some ways, forms authentication is a much easier authentication model to use because developers do not have to wrestle with the intricacies of IIS native authentication modules, UNC shares and ASP.NET’s impersonation settings. Tweaking some of the earlier samples to require forms authentication, the following output shows the results of running an application with the native AnonymousAuthenticationModule enabled in IIS and application impersonation enabled in ASP.NET. The OS thread identity when the page executes is: bhaidar-PC\test The managed thread identity when the page executes is: testuser The managed thread identity is of type: System.Security.Principal.GenericPrincipal The user on the HttpContext when the page executes is: testuser The user on the HttpContext is of type: System.Security.Principal.GenericPrincipal The request authenticated identity set by IIS is: NT AUTHORITY\NETWORK SERVICE As you can see, HttpContext and the current thread reflect the GenericPrincipal that is created by FormsAuthenticationModule. The fact that application impersonation is being used is ignored, as is the value of the impersonation token set by IIS. Also notice here that, since there is no native authentication module enabled on IIS 7.0 other than the AnonymousAuthenticationModule and the managed FormsAuthenticationModule, the impersonation token generated by IIS defaults to the identity currently configured for the worker process, in this case it is the NT AUTHORITY\NETWORK SERVICE identity. When developing with forms authentication, you probably should still be aware of the operating system thread identity because it is this identity that will be used when using some type of integrated security with back-end resources such as SQL Server. However, from a downstream authorization perspective, using forms authentication means that only the GenericPrincipal (and the contained FormsIdentity) are relevant when making authorization decisions. DefaultAuthentication and Thread.CurrentPrincipal Most of the sample output has included information about the identity of Thread.CurrentPrincipal and the identity on HttpContext.Current.User. However, in the previous discussions on Windows​ AuthenticationModule and FormsAuthenticationModule, you saw that these modules only set the value of the User property for the current context. How then did the same IPrincipal reference make it onto the CurrentPrincipal property of the current thread? The answer lies within the ASP.NET runtime. Since ASP.NET 1.0, there has been a “hidden” pipeline event called DefaultAuthentication. This event is not publicly exposed, so as Download at Boykma.Com 117 Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model a module author you cannot directly hook the event. However, there is an ASP.NET authentication module that runs during the DefaultAuthentication event called DefaultAuthentication module. As a developer, you never explicitly configure this module. Instead when the ASP.NET runtime is initializing an application and is hooking up all of the HttpModules it also automatically registers the DefaultAuthenticationModule. As a result, this module is always running in every ASP.NET application. There is no way to “turn off” or unregister the DefaultAuthenticationModule. This module provides a number of services for an ASP.NET application: 1. It exposes a public Authenticate event (like the other authentication modules) that a devel- oper can hook. When running an application in the IIS integrated mode, this event is not fired. It is required to register for the AuthenticateRequest event instead. Subscribing to the Authenticate event would throw a PlatformNotSupportedException. 2. It provides a default behavior for failed authentication attempts. 3. The module ensures that if the User property has not been set yet, a GenericPrincipal is cre- ated and set on the current context’s User property. 4. The module explicitly sets the CurrentPrincipal property of the current thread to the same value as the current context’s User property. Initially, DefaultAuthenticationModule looks at the value of Response.StatusCode, and if the status code is set to a value greater than 200, then the module routes the current request directly to the End​ Request pipeline event. Normally, unless a piece of code explicitly changes the value of Response​ .StatusCode, it defaults to 200 when the Response object is initially created. As a side effect of Default​ AuthenticationModule checking the StatusCode, if DefaultAuthenticationModule detects that Response.StatusCode was set to 401 (indicating an Access Denied error has occurred), the module writes out a custom 401 error message to Response prior to handing off the request to the EndRequest event. Note that neither WindowsAuthenticationModule nor FormsAuthenticationModule sets the StatusCode property. So, the behavior in DefaultAuthenticationModule around status codes is only useful for developers who write custom authentication mechanisms that explicitly set the StatusCode for failed authentication attempts. To see this behavior, look at a simple application with an HttpModule that hooks the Authenticate​ Request event. The module just sets the StatusCode property on the response to 401. The application has only the native AnonymousAuthenticationModule enabled in IIS (this prevents an IIS credentials prompt from occurring in the sample). In ASP.NET, the application has its authentication mode set to None, because the normal scenario for depending on the 401 behavior of DefaultAuthentication​ Module makes sense only when you write a custom authentication mechanism: 118 Download at Boykma.Com Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model C# public class ModuleThatForces401 : IHttpModule { //Default implementation details left out… private void FakeA401(Object source, EventArgs e) { HttpContext.Current.Response.StatusCode = 401; } public void Init(HttpApplication context) { context.AuthenticateRequest += new EventHandler(this.FakeA401); } } VB.NET Public Class ModuleThatForces401 Implements IHttpModule ‘ Default implementation details left out… Private Sub FakeA401(ByVal source As Object, ByVal e As EventArgs) HttpContext.Current.Response.StatusCode = 401 End Sub #Region “IHttpModule Members” Public Sub Dispose() Implements IHttpModule.Dispose Throw New Exception(“The method or operation is not implemented.”) End Sub Public Sub Init(ByVal context As HttpApplication) Implements IHttpModule.Init AddHandler context.AuthenticateRequest, AddressOf FakeA401 End Sub #End Region End Class Running a website with this module results in a custom error page containing an “Access is denied” error message generated by DefaultAuthenticationModule. The DefaultAuthenticationModule does not fire the Authenticate event when an application is running in the new IIS integrated mode. Therefore, if you want to provide custom authentication, you should develop an HttpModule and hook into the AuthenticateRequest event. Custom authentication code running in this event should create an IPrincipal and set it on the current context’s User property if the custom authentication succeeds. Optionally, you can set StatusCode to 401 (or some other error code depending on the type of failure). After the managed authentication module finishes executing, DefaultAuthenticationModule runs and looks at the StatusCode of the current authenticated request and will output custom error information if a 401 is in the StatusCode. Also, any StatusCode greater than 200 will cause the module to short-circuit the request and reroute it to the EndRequest pipeline event. Download at Boykma.Com 119 Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model If StatusCode is still set to 200 or lower and any custom authentication in the AuthenticateRequest event succeeds, the DefaultAuthenticationModule checks the current context’s User property. If the User property is still null (remember that the property defaults to null back when BeginRequest occurs), the module constructs a GenericPrincipal containing a GenericIdentity with the following characteristics: ❑❑ The username is set to the empty string. ❑❑ The authentication type is set to the empty string. ❑❑ A zero-length string array is assigned as the set of roles associated with the principal. ❑❑ The IsAuthenticated property in the identity returns false. The reason the module creates the GenericPrincipal is that most downstream authorization code expects some kind of IPrincipal to exist on the current HttpContext. If the module did not place at least a default IPrincipal implementation on the User property, developers would probably be plagued with null reference exceptions when various pieces of authorization code attempted to perform IsInRole checks. After ensuring that default principal exists, the module sets Thread.CurrentPrincipal to the same value as HttpContext.Current.User. It is this behavior that automatically ensures the thread principal and the context’s principal are properly synchronized. The fact that ASP.NET has an Http​ Context with a property for holding an IPrincipal creates the potential for an identity mismatch with the .NET Framework’s convention of storing an IPrincipal on the current thread. Having the DefaultAuthenticationModule synchronize the two values ensures that developers can use either the ASP.NET coding convention (HttpContext.Current.User) or the .NET Framework’s coding convention (Thread.CurrentPrinicpal) for referencing the current IPrincipal, and both coding styles will reference the same identity and result in the same security decisions. Another nice side effect of this synchronization is that developers using the declarative syntax for making access checks ([PrincipalPermission(SecurityAction.Demand, Role=“Administrators”] ) will also get the same behavior because PrincipalPermission internally performs an access check against Thread.CurrentPrincipal (not HttpContext.Current.User). However, when an application is running in integrated mode, things are much different. Given the native AnonymousAuthenticationModule is enabled, native WindowsAuthenticationModule is enabled, ASP.NET application is configured with Windows authentication, this means Anonymous​ AuthenticationModule takes control all over the authentication. It has been mentioned above that the native AnonymousAuthenticationModule when it finds that there is no native User principal set yet, it creates a new Windows anonymous principal and through an integrated request processing pipeline mechanism, the value is passed to the HttpContext.Current.User property. Therefore, when the DefaultAuthenticationModule runs, it will find out that the HttpContext.Current​ .User property already assigned a value (either an authenticated or anonymous WindowsPrincipal instance) and hence it does nothing and exits. PostAuthenticateRequest This event has already been added in ASP.NET 2.0, along with most of the other Post* events in the pipeline. The two ASP.NET modules that hook this event are the managed AnonymousIdentificationModule and RoleManagerModule. Of the two, only RoleManagerModule is actually involved in security-related work. The AnonymousIdentificationModule hooks PostAuthenticateRequest because it is early enough in the pipeline for it to issue an anonymous identifier for use with the Profile 120 Download at Boykma.Com Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model feature, but it is late enough in the pipeline that it can determine if the current user is authenticated, and thus an anonymous identifier would not be needed in that case. Because RoleManagerModule, and the Role Manager feature, is covered in much more detail later in the book, I will simply say at this point that the purpose of the RoleManagerModule is to create a RolePrincipal class and set it as the value for both HttpContext.Current.User and Thread​ .CurrentPrincipal. The RolePrincipal class fulfills IsInRole access checks with user-to-role mappings stored using the Role Manager feature. It is important for developers to understand that because the PostAuthenticateRequest event occurs after the DefaultAuthenticationModule has run, any changes made to either HttpContext​ .Current.User or Thread.CurrentPrincipal will not be automatically synchronized. For example, this is why RoleManagerModule has to set both the context and the thread’s principals. If the module did not perform this extra work, developers would be left with two different principals and two different sets of results from calling IPrincipal.IsInRole. A simple application that hooks PostAuthenticateRequest illustrates this subtle problem. The application uses forms authentication, which initially results in same GenericPrincipal on both the context’s User property and the current principal of the thread. However, the sample application changes the principal on HttpContext.Current.User to a completely different value during the PostAuthenticateRequest event. C# //Hook PostAuthenticateRequest inside of global.asax void Application_PostAuthenticateRequest(Object sender, EventArgs e) { IPrincipal p = HttpContext.Current.User; //Only reset the principal after having logged in with //forms authentication. if (p.Identity.IsAuthenticated) { GenericIdentity gi = new GenericIdentity(“CompletelyDifferentUser”, “”); string[] roles = new string[0]; HttpContext.Current.User = new GenericPrincipal(gi, roles); //Ooops - forgot to sync up with Thread.CurrentPrincipal!! } } VB.NET Private Sub Application_PostAuthenticateRequest(ByVal sender As Object, _ ByVal e As EventArgs) Dim p As IPrincipal = HttpContext.Current.User ‘Only reset the principal after having logged in with ‘forms authentication. Download at Boykma.Com 121 Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model If p.Identity.IsAuthenticated Then Dim gi As New GenericIdentity(“CompletelyDifferentUser”, “”) Dim roles(-1) As String HttpContext.Current.User = New GenericPrincipal(gi, roles) ‘Ooops - forgot to sync up with Thread.CurrentPrincipal!! End If End Sub The resulting output shows the mismatch between the thread principal and the context’s principal. The testuser account is the identity that was logged in with forms authentication. The managed thread identity when the page executes is: testuser The managed thread identity is of type: System.Security.Principal.GenericPrincipal The user on the HttpContext when the page executes is: CompletelyDifferentUser The user on the HttpContext is of type: System.Security.Principal.GenericPrincipal The user on the HttpContext and the thread principal point at the same object: False Now in practice you would not create a new identity during PostAuthenticateRequest. However, you may have a custom mechanism for populating roles, much like the Role Manager feature, whereby the roles for a user are established after an IIdentity implementation has been created for a user. Hooking PostAuthenticateRequest is a logical choice because by this point you are guaranteed to have some type of IIdentity implementation available off of the context. But as shown previously, if you reset the principal during PostAuthenticateRequest, it is your responsibility to also set the value on Thread.CurrentPrincipal to prevent mismatches later on in the pipeline. AuthorizeRequest Now you will turn your attention to the portion of the pipeline that authorizes users to content and pages. As the name of the pipeline event implies, decisions on whether the current user is allowed to continue are made during this pipeline event. ASP.NET ships with two HttpModules configured in the or section that enforce authorization: ❑❑ FileAuthorizationModule ❑❑ UrlAuthorizationModule If you have configured the application to run in the unified integrated mode, the section entries will take effect, else if the application is operating in the classic mode, then the old usual section takes effect. In addition, IIS adds a new native URLAuthorizationModule that you can enable to run for all content file types. Configuring this module is similar to the way you configure URLAuthorizationModule in ASP.NET. The module will be discussed shortly. Developers can hook this event and provide their own custom authorization implementations as well, whether it is through native or managed code. By the time the AuthorizeRequest event occurs, the 122 Download at Boykma.Com Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model IPrincipal references for the current context and the current thread have been set and should be stable for the remainder of the request. Although it is technically possible to change either of these identities during this event (or any other event later in the pipeline), this is not a practice you want to adopt! FileAuthorizationModule FileAuthorizationModule authorizes access to content by checking the ACLs on the underlying requested file and confirming that the current user has either read or read/write access (more on what defines the “current user” in a bit). For HEAD, GET, and POST requests, the module checks for read access. For all other verbs, the module checks for both read and write access. Because ACL checks only make sense when working with a WindowsIdentity, FileAuthorization​ Module is really only useful if both of the following are true: ❑❑ The ASP.NET application uses Windows authentication. ❑❑ The ASP.NET application is not running on a UNC share. If an ASP.NET application is running on a UNC share, FileAuthorizationModule does not attempt any file ACL checks. Instead it just immediately exits. The module has this behavior because UNC based ASP.NET applications run with the explicit UNC credentials. If these credentials did not have access to all of the files on the UNC share, the application would fail in IIS anyway. As a result, performing a file ACL check is redundant (the app made it far enough to start running in ASP.NET; therefore, the UNC identity has access to the share). Although configuring FileAuthorizationModule in web.config configuration file for these types of applications is innocuous, developers should probably remove File​ AuthorizationModule from their configuration files because it serves no purpose in the UNC case. Because FileAuthorizationModule performs file ACL checks, it requires that a WindowsIdentity be available on HttpContext.Current.User. If some other type of IIdentity implementation is on the User property, the module automatically grants access and immediately exits. This means file ACLs are not checked when the authentication mode is set to Forms or None. Assuming that you are using Windows authentication in ASP.NET, the question arises on how to use file ACL checks when the AnonymousAuthenticationModule is enabled in IIS. If your site has a mixture of public and private content, you can set more restrictive ACLs on the private content. If an unauthenticated browser user attempts to access the private content, then FileAuthorizationModule will force the browser to authenticate itself (more on this later). If an authenticated user is allowed access to the file, then he or she will be able to access the private content. The user token that the FileAuthorizationModule uses for making the access check is the request authenticated identity set by IIS. From earlier topics, you know that in non-UNC scenarios, the request authenticated identity is either IUSR or the token associated with an authenticated browser user. This means that if you want to grant access to anonymous users, what you really need to do is set the NTFS ACLs on the filesystem to allow read (or read/write access depending the HTTP verbs being used) access to the IUSR account. If you happened to change the default anonymous user account in the IIS 7.0 Manager tool or through the section in the web.config configuration file, you would grant access to whatever anonymous user account is currently configured for the application in IIS. You can see this behavior pretty easily by explicitly denying access for IUSR when you set up the ACLs for a file. In IIS, set the application to only allow Anonymous access, i.e., enabling the native Anonymous​ Download at Boykma.Com 123 Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model AuthenticationModule; this prevents IIS from attempting to negotiate an authenticated identity with the browser. Now when you try to browse to the file, FileAuthorizationModule will return a 401 status code and write out some custom error information stating that access is denied. If you then grant access on the file to IUSR again, you will be able to successfully browse to the file. Because it is the request authenticated identity set by IIS that is used for file ACL checks by the module, other security identities are ignored by FileAuthorizationModule. For example, if you are using application impersonation, the operating system thread identity will be running as the application impersonation identity. Although technically nothing prevents you from using application impersonation with file authorization, application impersonation does not affect the request authenticated identity set by IIS. Because FileAuthorizationModule does not use the operating system thread identity for its access checks, it ignores the effects of application impersonation and instead the access checks will always be made against the anonymous or authenticated user account from IIS. The concept to always remember when using FileAuthorizationModule is that only the anonymous user account from IIS or the authenticated browser user will be used for the access checks. This also means that an application needs to run with client impersonation (that is, for file authorization checks to really make any sense. When FileAuthorizationModule determines that the identity represented by the IIS request authenticated identity does not have read (or read/write access depending on the HTTP verb used), it sets Response.StatusCode to 401, writes custom error information indicating that access is denied, and reroutes the request to the EndRequest event in the pipeline. If the application is configured in IIS to allow authenticated access as part of the security options, when the 401 result is detected by IIS, it will attempt to negotiate an authenticated connection with the browser after the 401 occurs. If this negotiation succeeds, the next request to ASP.NET will be made as an authenticated browser identity. Of course, if the authenticated browser identity also lacks the appropriate file access, the subsequent 401 error results in the custom error information from the ASP.NET module, and no additional authentication negotiation with the browser occurs. Managed UrlAuthorizationModule Because an authorization strategy tightly tied to Windows security identities is not always useful for Internet-facing applications, a more generic authorization mechanism is implemented in Url AuthorizationModule. Based on the URL authorization rules defined in configuration, the module uses the IPrincipal on the User property of the current context to compare against the users and roles that are defined in the authorization rules. Because URL authorization works only against the User property and the configuration-based authorization rules, it can be used with any type of authentication that sets an IPrincipal on the current context’s User property. For example, if you use Windows authentication with UrlAuthorizationModule, the module uses the WindowsIdentity in the context’s User property in a generic fashion. The module does not “know” the extra security semantics available from Windows authenticated users. Instead, the module performs its access checks based solely off of the value of the Name property on the associated IIdentity and the results of calling IPrincipal.IsInRole. As with file authorization, URL authorization also does not depend on the operating system thread identity. However, URL authorization can be used in conjunction with file authorization. Remember from previous topics though that the security identity represented by the IIS impersonation token will not necessarily match the IPrincipal in the User property on the current context. In the case of 124 Download at Boykma.Com Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model unauthenticated browser users and Windows authentication, the User property will contain a dummy principal (username set to empty string) while the request authenticated identity represents the anonymous access account configured in IIS. Because of this, be careful when mixing file and URL authorization, and keep in mind the different identities that each authorization module depends on. Before attempting any type of authorization, UrlAuthorizationModule first checks to see if the value of HttpContext.Current.SkipAuthorization is set to true. Authentication modules have the option of setting this property to true as a hint to UrlAuthorizationModule. As mentioned earlier, one example of this is FormsAuthenticationModule, which indicates that authorization should be skipped when a user requests the forms authentication login page. If SkipAuthorization is set to true, UrlAuthorizationModule immediately exits, and no further work is performed. The module delegates the actual work of authorizing the current User to the AuthorizationSection configuration class. This class is the root of the portion of the configuration hierarchy that defines the configuration element and all of the nested authorization rules. Because definitions can be made at the level of the machine, website, application or an individual subdirectory, the AuthorizationSection class merges the rules from the hierarchy of applicable configuration files to determine the set of rules that apply for the given page. Note that because of the merge behavior, the authorization rules defined in configuration files at the most granular configuration level take precedence. For example, this means authorization rules defined in a subdirectory are evaluated before authorization rules defined at the application level. The default authorization rules that ship with ASP.NET are defined in the root web.config configuration file located at: %windir%\Microsoft.NET\Framework\v2.0.50727\CONFIG\web.config The default rules just grant access to everyone: However, rules can either allow or deny access and can do so based on a combination of username, roles, and HTTP verbs. For example: After the merged set of rules have been determined, each authorization rule (defined with or elements) is iterated over sequentially. The result from the first authorization rule that matches either the name (User.Identity.Name) or one of the roles (User.IsInRole) is used as the authorization decision. The sequential nature of the authorization processing has two implications: 1. It is up to you to order the authorization rules in configuration so that they are evaluated in the correct order. For example, having a rule that allows access to a user based on a role precede a rule that denies access to the same user based on name results in the user always being granted access. ASP.NET does not perform any automatic rule reordering. 2. A URL authorization check is a linear walk of all authorization rules. From a performance per- spective, for a specific resource or directory you should place the most commonly applicable Download at Boykma.Com 125 Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model rules at the top of the section. For example, if you need to deny access on a resource for most users, but you allow access to only a small subset of these users, it makes sense to put the element first because that is the most common case. Using a simple application with a few pages, subdirectories, and authorization rules, we can get a better idea of the merge behavior and rule ordering behavior for URL authorization. The directory structure for the sample application is shown in Figure 3-7. Figure 3-7 There is an .aspx page located in the application root, as well as in each of the two subdirectories. The application uses forms authentication, with three fixed users defined in the configuration: The web.config configuration file located in the root of the application initially defines authorization rules as: 126 Download at Boykma.Com Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model When attempting to browse to any page in the application, you must log in as the Admin user to successfully reach the page. However, you can add a web.config configuration file into Directory A with the following authorization rule: Now both the Admin user and the DirectoryAUser can access the web page located in Directory A. The reason for this is that, as mentioned earlier, AuthorizationSection merges authorization rules from the bottom up. The result of defining rules in a web.config configuration file located in a subdirectory as well as in the application’s web.config configuration file is the following evaluation order: 1. First, rules from Directory A are evaluated. 2. If no match is found based on the combination of verbs, users and roles, then the rules from the application’s web.config configuration file are evaluated. 3. If no match was found using the application’s web.config configuration file, then the root web.config configuration file located in the framework CONFIG directory is evaluated. Remember that the default authorization configuration grants access to all users. With this evaluation order, DirectoryAUser matches the rule defined in the web.config configuration file located in Directory A. However, for the Admin user, no rules matched, so instead the rules in the application’s web.config configuration file are consulted. Now add a third web.config configuration file, this time dropping it into Directory B. This configuration file defines the following authorization rule: Because the evaluation order for accessing pages in Directory B will first reference the web.config configuration file from Directory B, the DirectoryBUser has access to files in the directory. If you log in though with DirectoryAUser, you will find that you can still access the files in Directory B. The reason is that when there is a rule evaluation miss from the web.config configuration file in Directory B, ASP.NET moves up the configuration hierarchy to next available web.config configuration file—in this case, the one located in Directory A. Because that web.config configuration file grants access to DirectoryAUser, that user can also access all resources in Directory B. The same effect of hierarchal configuration evaluation allows the Admin user access to the all resources in Directory B because the application’s web.config configuration file grants access to Admin. You can also get the same effect, and still centralize authorization rules in a single configuration file, by using configuration elements. Using tags, the authorization rules for the subdirectories are instead defined in the application’s main web.config configuration file: Download at Boykma.Com 127 Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model You will have the exact the same login behavior as described earlier when using separate web.config configuration files. The configuration system treats each tag as a logically separate “configuration” file. The end result is that even though the authorization rules are defined in the same physical web.config configuration file, the tags preserve the hierarchal nature of the configuration definitions. Developers sometimes want to control configuration in a central configuration file for an entire web server but are unsure of the value to use for the path attribute when referencing individual web applications. For example, if you want to centrally define configuration for an application called “Test” located in the Default Web Site in IIS, you can use the following definition: So far, the sample application has demonstrated the hierarchal merge behavior of different configuration files and different elements. If the authorization rule for the Admin user is reversed with the deny rule: the Admin user can no longer access any of the pages. The behavior for DirectoryBUser and DirectoryA​ User remains the same because the other elements grant these users access. But when the last set of authorization rules are evaluated, the blanket is evaluated first. As a result, any authorization evaluation that reaches this element always results in access being denied. Note that even though the previous samples relied on authorizing based on the user’s name; the same logic applies when authorizing based on verb or based on a set of one or more roles. Of course, what cannot be shown here (but you will see the behavior if you download and try out the sample) is the behavior when UrlAuthorizationModule denies access to a user. When the module denies access, it sets Response.StatusCode to 401, writes out some custom error text in the response, and then short circuits the request by rerouting it to the EndRequest event (basically, the same behavior as the FileAuthorizationModule). However, for those of you that have used URL authorization before, 128 Download at Boykma.Com Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model you know that typically you do not see an access denied error page. Instead, in the case of forms authentication, the browser user is redirected to the login page configured for forms authentication. If an application is using Windows authentication and configured to run in the Classic mode, the 401 is a signal to IIS to attempt to negotiate credentials with the browser based on the application’s security settings in IIS. If, however, the application is configured to run in the new Intgrated mode and Windows authentication is enabled, the user will get the chance to see the error text on the screen. Again this is because the application is running in the unified processing pipeline. When the AuthorizeReqesut event fires IIS runtime checks the configured authorization modules. If a native authorization module is enabled, IIS core engine starts executing it and if there is a managed authorization module enabled, ASP.NET will take care of processing the authorization process, let it be file or URL authorization How Character Sets Affect URL Authorization The character set used to populate the IPrincipal on the context’s User property plays an important role when authorizing access with UrlAuthorizationModule. When performing an access check based on the users attribute defined for an authorization rule, UrlAuthorizationModule performs a caseinsensitive string comparison with the value from HttpContext.Current.User.Name. Furthermore, the comparison is made using the casing rules for the invariant culture and ordering rules based on ordinal sort ordering. Because of this, there may be subtle mismatches in character comparisons due to a different character set being used for the value of a username. For example, the Membership feature in ASP.NET 3.5 stores usernames in a SQL Server database by default. If a website selects a different collation order than the default Latin collation, the character comparison rules that are applied at user creation time will not be the same as the comparison rules UrlAuthorizationModule applies when comparing usernames. Overall though, there are two simple approaches to avoid any problems caused by using different character sets for user creation and user authorization: ❑❑ Do not authorize based on usernames. Instead, only authorize based on roles because the likelihood of any organization creating two role names that differ only in characters with culturespecific semantics is extremely low. ❑❑ Use a character set/collation order in your back-end user store that is a close match with the invariant culture. For SQL Server, the default Latin collation is a pretty close approximation of the invariant culture. If you are authorizing against WindowsIdentity instances, then you won’t encounter a problem because usernames in Active Directory are just plain Unicode strings without culture-specific character handling. Native UrlAuthorizationModule IIS 7.0 introduces a new native URLAuthorizationModule that allows administrators or developers to configure URL authorization for an entire application or for a single page within the application. It is a much advanced and improved module over the previous authorization modules that used to ship with previous releases of IIS. Before the days of IIS 7.0, authorization was based on ACLs and Windows accounts only. This means that when you want to set authorization rules for Windows users or groups on resources in an application, you would configure ACLs for specific files or folders located in an application. Depending on ACLs only, limits the authorization to files and folder only, without being able to use this feature for real URLs. In addition, in previous releases of IIS, only Windows accounts or groups can be used for file or folder authorization. Download at Boykma.Com 129 Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model The new native URLAuthorizationModule introduced by IIS 7.0 has many features: ❑❑ It can be used to enable authorization for all content types served by IIS. This means, you can enable the new module to authorize access for an entire application regardless of the content file types included inside it. ❑❑ It enables authorization rules for both Windows and non-Windows user accounts. Non-Windows user accounts can be users configured with the application’s ASP.NET Membership and Role management services. ❑❑ It can be used to enable authorization for an entire application or for a specific page URL within an application. ❑❑ It has the ability to function properly when the managed FormsAuthenticationModule is enabled. The native URLAuthorizationModule can detect and parse FormsAuthenticion​ Tickets, hence being able to retrieve the authenticated username of the current request and execute the authorization rules to authorize the currently authenticated user. IIS 7.0 supports the new native module by shipping a new authorization rules UI interface. Selecting an application in the Features View of IIS, you will notice a new icon called Authorization Rules. Figure 3-8 shows the new UI displayed when the Authorization Rules icon is double-clicked. Figure 3-8 On the Actions menu you see two links to configure the authorization rules for an application. The Add Allow Rule… is used to add a new Allow authorization rule to allow a user or group to access the application. On the other hand, the Add Deny Rule… is used to provide a Deny rule to prevent a user or group from accessing the application. Both links have a similar UI window. The only difference is that one will add an Allow rule and the other will add a Deny rule. Figure 3-9 shows the UI window that ships with IIS 7.0 to configure URL authorization rules. 130 Download at Boykma.Com Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model Figure 3-9 You can add URL authorization rules based on the following criteria: ❑❑ To allow all users to access the application ❑❑ To allow only anonymous users to access the application ❑❑ To allow specific role(s) or group(s) to access the application. You can specify multiple roles or users separated by a comma (,) and can be mixed between Windows groups and ASP.NET roles. ❑❑ To allow specific users to access the application. You can specify multiple users separated by a comma (,) and can be mixed between Windows users and ASP.NET Membership users. You can also apply the rule you add to a specific verb by setting the value of the last textbox on the UI window. If you look back at Figure 3-9, you will notice two handy links to add ASP.NET users and roles. If you have already enabled membership and role management services in an application, you will be able to view the list of all users and roles that are stored in the database of the application. Figure 3-10 shows a listing of users that are configured in the application. These steps show you how to configure URL authorization for an entire application. What if you want to configure URL authorization for a single page? This task has been made easy with the new native authorization module. Click the application name on the tree of applications in the IIS 7.0 Manager tool, and then click the Content View tab. A list of all the resources inside the application is listed. Right-click the specific resource and choose Switch to Features View. You will see a new node with the resource name selected underneath the application on the tree of applications. Clicking this new node enables Download at Boykma.Com 131 Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model you to configure different features per resource in IIS 7.0. You can now follow the same steps taken above to add authorization rules for the specific resource in the application. Figure 3-10 Figure 3-11 shows the Features view for a specific selected resource. 132 Figure 3-11 Download at Boykma.Com Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model The new native URLAuthorization module can also be configured using the web.config configuration file of an application. The section located inside the section group in the section group is used to configure URL authorization for an application or a single page inside the web.config configuration file. Figure 3-12 shows the hierarchy of a sample application that is used to demonstrate configuring native URL authorization through configuration settings. Figure 3-12 The application assumes the following settings: ❑❑ AnonymousAuthenticationModule is enabled on IIS. ❑❑ BasicAuthenticationModule is enabled on IIS. In addition, two new Windows accounts have been created: bhaidar and test. These accounts will be used with the basic authentication. The plan is to allow bhaidar to access the entire application and deny access to the test account. In addition, the DirectoryA_Default.aspx page should allow access to the test user account only. To add the configuration settings, you can either use the IIS Manager tool or add the configuration settings yourself into the web.config configuration file. We will select the second option for demonstration purposes. First of all make sure the native URLAuthorizationModule is installed. Go to the application’s web​ .config configuration file and locate the section group. You will need to add Download at Boykma.Com 133 Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model a sub-section group called the section group. To enable URL authorization, you will need to add the rules inside the section. First of all, the default URL authorization rule added by IIS 7.0 is removed. To remove the default rule completely, make sure to empty the roles and verbs attributes, and set * as the value for the users attribute. Removing the authorization rule allows all users to access the application. A new authorization rule is added to allow access to the Windows user account bhaidar. The access​ Type attribute is an enumeration that can take as a value either Allow or Deny. Another authorization rule is added that denies the user test. Inside the Directory_A, add a new web.config configuration file to configure the authorization rules for the only .aspx page included inside it. A new location element is created to configure the authorization settings for a single .aspx page. Going back to the requirements, only the test user account should be allowed access to this .aspx page. Therefore, two new authorization rules are added to remove the propagating effect of the authorization rule that was set at the application level and that grants access to the bhaidar user account. In addition, you should also remove the propagation effect of the authorization rule that denies access to the test account set on the application level. Finally, add a new authorization rule that allows access to the test user account on the .aspx page. A major difference between the managed URL authorization and the native URL authorization can be summarized in the following: ❑❑ Managed URL authorization is configured by default to serve only managed resources, whereas the native URL authorization is enabled to serve all content types. This can be solved by removing the managed URLAuthorizationModule entry and adding it once again by skipping out the preCondition attribute or setting its value to an empty string (double quotes). 134 Download at Boykma.Com Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model ❑❑ Managed URL authorization starts calculating the authorization rules following the Bottomup strategy that has been previously explained. On the other hand, the native URL authorization calculates the authorization rules following the top-down strategy meaning that authorization rules at the parent will be evaluated first. In addition, the native URLAuthorizationModule evaluates Deny rules before evaluating the Allow rules. IIS will execute the enabled modules according to their order of appearance when they were registered in the configuration section with the exception of programmatic ordering that only native modules can do. The same rule applies on the managed and native URLAuthorizationModules. Depending on the order of appearance, those modules would execute. The important thing to remember here is that no matter who runs and executes first, any of the aforementioned modules, if the authorization process fails, the SkipAuthorization property on the HttpContext class will be set to true. For instance, if the native module runs first and the authorization process fails, when it is time for the managed module to run, it will check the SkipAuthorization property. If the value is true, then the managed module will not run, else it will run and authorize the request. PostAuthorizeRequest Through PreRequestHandlerExecute After the AuthorizeRequest event, developers can hook the PostAuthorizeRequest event if there is custom authorization work that needs to be performed. ASP.NET does not ship with any HttpModules that hook this event. After PostAuthorizeRequest, there are no other pipeline events intended for authentication- or authorization-related processing. Although many of the subsequent pipeline events may use the identity of the current user, the pipeline events up through PreRequestHandlerExecute are intended for setting up and initializing other information, such as session state data or cached information used by output and fragment caching. Technically, you could manipulate the operating system thread identity, the current thread principal, or the current context’s User property during any subsequent pipeline event. However, there is an implicit assumption that after PostAuthenticateRequest the security information for the request is stable, and that after PostAuthorizeRequest no additional authorization is necessary. Because the pipeline events after PostAuthorizeRequest are involved in retrieving data tied to a user identity (state and cached data), it is important that any custom authentication or authorization mechanism honors these assumptions. Blocking Requests at the IIS Level IIS 7.0 replaces the old URLScan security add-on with a new native module, RequestFiltering module. The new module is configurable through the configuration settings just like any other native module configured in the ApplicationHost.config configuration file. The module includes all the core features of the URLScan add-on and adds a new feature called Hidden Segments. If you open the ApplicationHost.config configuration file, you will notice the configuration section group: Download at Boykma.Com 135 Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model ..... The configuration section lists all the file extensions that are not allowed to be accessed directly by users. For instance, you may notice an entry for the .ascx extension, which represents the extension for ASP.NET UserControls. In addition, the allowUnlisted attribute is a Boolean value, which takes either false or true. By default, it has the value of true. This means the configuration section allows requests to all the file types except those listed inside it. The RequestFiltering module runs before any request-processing pipeline happens inside the IIS engine. That is why ASP.NET now delegates preventing access to sensitive file type extensions to this native module. This is done by listing all the file type extensions for which ASP.NET has configured HttpHandlers to prevent access. As with other configuration sections, you can configure the file extensions from inside the application’s web.config configuration file. For instance, to prevent access to content file types with an extension of .asp, you add the following to the configuration file: When a resource with an extension of .asp is requested through the browser, an error page prepared by the IIS engine is displayed to the user, as shown in Figure 3-13. 136 Download at Boykma.Com Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model Figure 3-13 You have full control over the allowed and prevented extensions in an application. But remember that the configuration section group is locked down by default and to be able to edit its sections in the application’s web.config configuration file, the module’s overrideModeDefault attribute should be set to Allow in the ApplicationHost.config configuration file.
Another important configuration section inside the configuration section group is the section. You can use this section to reject URLs that contain certain segments. For instance, if you try to access a URL such as http://localhost/Request​ FilteringSample/bin/, you will get an error page prepared by IIS that mentions the existence of a hiddenSegment that denies access to URLs containing the bin segment. In addition, all ASP.NET system folders are added to this section and considered to be sensitive segments that no URL should have direct access to. You can also add any segment you want to the default list found in the ApplicationH​ost.config configuration file. IIS makes sure to reject any direct access to the hidden segments specified. The RequestFiltering module has several important security features that are worth looking at. You can read more on this new native module by checking the online resource at http://learn.iis.net/ page.aspx/143/how-to-use-request-filtering/. Identity during Asynchronous Page Execution Earlier in the chapter, I discussed issues with flowing security identities through asynchronous pipeline event handlers. The Page handler in ASP.NET 3.5 also supports the concept of asynchronous execution, and as a result, developers using this functionality should be aware of the security identities for this case. Download at Boykma.Com 137 Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model Things can be a little confusing with asynchronous pages because the Page class supports two different patterns for carrying out asynchronous tasks. Both approaches, along with the flow of security information, are discussed in the next two sections. Asynchronous PreRender Processing A developer can request asynchronous support in a page by including the Async attribute in the page directive: C# <%@ Page Language=”C#” Async=”true” %> VB.NET <%@ Page Language=”VB” Async=”true” %> To leverage this asynchronous page model, you need to register begin and end event handlers for your asynchronous task. This approach is exactly the same model as discussed earlier for asynchronous pipeline events. You typically hook up the async begin and end event handlers inside of a page or control event where a long-running task would normally occur. For example, instead of making a call to a high-latency Web Service from inside of a button click event handler, you would instead register your asynchronous event handlers in the click event handler. Furthermore, you can hook up multiple begin and end event handlers, and ASP.NET will call each pair of asynchronous event handlers in sequence. ASP.NET calls into your async begin event handler after the PreRender phase of the page life cycle. The idea is that high-latency work can be safely deferred until the PreRender phase because the results of any processing are not needed until the subsequent Render phase of a Page. Inside of your async begin event handler, you collect whatever data you need to pass to your asynchronous task (page variables, context data, and so on), and then you invoke the asynchronous task. As with asynchronous pipeline events, the asynchronous task that is called during asynchronous page processing runs on a .NET thread-pool thread. This means it is your responsibility to gather any necessary security information and “throw it over the wall” to the asynchronous task. After some indeterminate amount of time has passed, the asynchronous task completes and the ASP.NET runtime is signaled via a callback. Just as you saw with asynchronous pipeline events, the async end event for pages executes on a thread-pool thread. The operating system thread identity at this point will not reflect the security settings you have set in IIS and ASP.NET. Note though that if you implement your async begin and end event handlers as part of the page’s code-behind class, you can always get back to the HttpContext associated with the page (that is, this.Context is available). This at least gives you access to the IPrincipal associated with the request from inside of both the async begin and end event handlers. After the end event handler runs, ASP.NET reschedules the page for execution, at which point ASP.NET reinitializes the operating system thread identity, managed thread identity, and the HttpContext (including its associated IPrincipal) for the current managed thread. To demonstrate the security identity handling during asynchronous page execution, you can create an application with a single asynchronous page that registers for asynchronous PreRender handling. The page has a single button on it, and the application registers the async begin and end event handlers in its click event. 138 Download at Boykma.Com Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model C# protected void Button1_Click(object sender, EventArgs e) { //Hook up the async begin and end events BeginEventHandler bh = new BeginEventHandler(this.BeginAsyncPageProcessing); EndEventHandler eh = new EndEventHandler(this.EndAsyncPageProcessing); AddOnPreRenderCompleteAsync(bh, eh); } VB.NET Protected Sub Button1_Click(ByVal sender As Object, ByVal e As EventArgs) ‘Hook up the async begin and end events Dim bh As New BeginEventHandler(AddressOf Me.BeginAsyncPageProcessing) Dim eh As New EndEventHandler(AddressOf Me.EndAsyncPageProcessing) AddOnPreRenderCompleteAsync(bh, eh) End Sub Notice that the event handler delegates are of the exact same type used with asynchronous pipeline events. The async begin handler is responsible for triggering the asynchronous work and returns the IAsyncResult reference to ASP.NET. C# // Defined as part of the page class public delegate void AsyncSleepDelegate(); private IAsyncResult BeginAsyncPageProcessing( object sender, EventArgs e, AsyncCallback cb, object extraData) { //Output the security information //.. code snipped out for brevity … //Do the actual asynchronous work Sleep s = new Sleep(this.Context.Items); AsyncSleepDelegate asd = new AsyncSleepDelegate(s.DoWork); return asd.BeginInvoke(cb, asd); } VB.NET ‘ Defined as part of the page class Public Delegate Sub AsyncSleepDelegate() Private Function BeginAsyncPageProcessing(ByVal sender As Object, _ ByVal e As EventArgs, _ ByVal cb As AsyncCallback, _ ByVal extraData As Object) _ As IAsyncResult ‘Output the security information Download at Boykma.Com 139 Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model ‘.. code snipped out for brevity … ‘Do the actual asynchronous work Dim s As New Sleep(Me.Context.Items) Dim asd As New AsyncSleepDelegate(AddressOf s.DoWork) Return asd.BeginInvoke(cb, asd) End Function The async end event handler in the sample application just outputs more security identity information. In a real application, you would gather the results of the asynchronous work and probably set the values of various controls on the page or perhaps data-bind the results to one of the data controls. C# private void EndAsyncPageProcessing(IAsyncResult ar) { //Normally you would harvest the results of async processing here AsyncSleepDelegate asd = (AsyncSleepDelegate)ar.AsyncState; asd.EndInvoke(ar); //Output security information //.. code snipped out for brevity … } VB.NET Private Sub EndAsyncPageProcessing(ByVal ar As IAsyncResult) ‘Normally you would harvest the results of async processing here Dim asd As AsyncSleepDelegate = CType(ar.AsyncState, AsyncSleepDelegate) asd.EndInvoke(ar) ‘Output the security information ‘.. code snipped out for brevity … End Sub As with the asynchronous pipeline event sample, the asynchronous page uses a simple class that sleeps for one second to simulate a long-running task. A reference to the current HttpContext is passed in the constructor so that the class can log the operating system thread identity. C# public class Sleep { private IDictionary state; 140 public Sleep(IDictionary appState) { state = appState; } public void DoWork() { state[“AsyncWorkerClass_OperatingSystemThreadIdentity”] = Download at Boykma.Com Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model WindowsIdentity.GetCurrent().Name; Thread.Sleep(1000); } } VB.NET Public Class Sleep Private state As IDictionary Public Sub New(ByVal appState As IDictionary) state = appState End Sub Public Sub DoWork() state(“AsyncWorkerClass_OperatingSystemThreadIdentity”) = _ WindowsIdentity.GetCurrent().Name() Thread.Sleep(1000) End Sub End Class I ran the sample application with the following IIS and ASP.NET configuration settings: 1. The application ran locally on the web server. 2. Authenticated access was required in IIS. 3. An explicit application impersonation identity was used for ASP.NET. The results of running the application with this configuration are shown here: The OS thread identity during the beginning of page async processing is: bhaidar-PC\test The OS thread identity in the async worker class is: NT AUTHORITY\NETWORK SERVICE The OS thread identity during the end of page async processing is: NT AUTHORITY\ NETWORK SERVICE The OS thread identity in Render is: bhaidar-PC\test You can see that the background worker and the end event run with the default credentials of the process, despite the fact that the ASP.NET application is configured with application impersonation. Once the page starts running again in the Render event, though, ASP.NET has reinitialized all of the security information, and the application impersonation identity is once again used for the operating system thread identity. The exact same approaches for flowing credentials discussed earlier in the section “Thread Identity and Asynchronous Pipeline Events” also apply to the asynchronous PreRender processing. Asynchronous Page Using PageAsyncTask An alternative approach to attributing a page as being is the concept of asynchronous page tasks. This second approach has many similarities to the previous discussion. As a developer, you still need to delegate your high-latency work as a piece of asynchronous processing. Additionally, you hook into the PageAsyncTask-based processing with a pair of begin and end event handlers. Download at Boykma.Com 141 Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model However, there are some important differences in the PageAsyncTask approach. You can create one or more asynchronous units of work, wrap each piece of work with individual PageAsyncTask instances, and then hand all of the work off as a single “package” to the page. With the PreRender-based approach, handling multiple asynchronous tasks is a little more awkward because you either have to coalesce all of the work yourself inside of a custom class, or you have to carefully hook up a chain of begin and end event handlers. Also, when you are wrapping your asynchronous work, you can pass a timeout handler to the PageAsyncTask that will execute if your asynchronous work takes too long. The actual timeout that is honored for each piece of asynchronous work defaults to 45 seconds, though this can be changed by setting the AsyncTimeout property on the page, or by setting an application-wide default in the configuration section. There is also an option to allow all or some of the asynchronous work to execute in parallel. For example, if a web page required three lengthy web service calls to fetch data, you could indicate to ASP.NET that all three asynchronous tasks should be kicked off in parallel on separate worker threads. Once you have wrapped your asynchronous task with one or more instances of PageAsyncTask, you register the instances with the Page using the RegisterAsyncTask method. At this point, you have one of two options: you can do nothing else, in which case ASP.NET will call your asynchronous work immediately after the PreRender event. You can also take control of exactly when you want the page to stop normal processing by explicitly calling the ExecuteRegisteredAsyncTasks method. Personally, I think it is more intuitive to explicitly trigger asynchronous processing in a click event handler, as opposed to waiting for the default PreRender processing. Up to this point, the differences between PageAsycTask-based processing and the default PreRender processing have all been in the area of programmability and flexibility. The interesting security behavior around PageAsyncTask-based processing is that ASP.NET will actually reinitialize the operating system thread identity, managed thread identity, and HttpContext for the end event handler. Note that you are still responsible for flowing security information to your asynchronous work, but now ASP.NET at least ensures a balanced set of security information in both the begin and end event handlers. To highlight this behavior, modify the PreRender example to instead use a PageAsyncTask. The only difference is that the button click handler has been modified: C# protected void Button1_Click(object sender, EventArgs e) { //Hook up the async begin and end events //using the PageAsyncTask pattern BeginEventHandler bh = new BeginEventHandler(this.BeginAsyncPageProcessing); EndEventHandler eh = new EndEventHandler(this.EndAsyncPageProcessing); Object someState = new Object(); PageAsyncTask pt = new PageAsyncTask(bh, eh, null, someState); this.RegisterAsyncTask(pt); //Explicitly trigger the async page task at this point 142 Download at Boykma.Com Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model //rather than waiting for PreRender to occur this.ExecuteRegisteredAsyncTasks(); } VB.NET Protected Sub Button1_Click(ByVal sender As Object, ByVal e As EventArgs) ‘Hook up the async begin and end events ‘using the PageAsyncTask pattern Dim bh As New BeginEventHandler(AddressOf Me.BeginAsyncPageProcessing) Dim eh As New EndEventHandler(AddressOf Me.EndAsyncPageProcessing) Dim someState As New Object() Dim pt As New PageAsyncTask(bh, eh, Nothing, someState) Me.RegisterAsyncTask(pt) ‘Explicitly trigger the async page task at this point ‘rather than waiting for PreRender to occur Me.ExecuteRegisteredAsyncTasks() End Sub Notice that the begin and end event handlers use the same definitions. However, instead of calling AddOnPreRenderCompleteAsync, the page wraps the event handlers in an instance of PageAsyncTask (in this case, no timeout event handler is registered) and registers the asynchronous task with the page. Last, the button click event handler explicitly triggers the execution of the asynchronous work. Everything else in the sample application remains the same. Running with the same IIS and ASP.NET configuration as before (local application, application impersonation enabled, authenticated access required in IIS), the output looks like this: The OS thread identity during the beginning of page async processing is: bhaidar-PC\test The OS thread identity in the async worker class is: NT AUTHORITY\NETWORK SERVICE The OS thread identity during the end of page async processing is: bhaidar-PC\test The OS thread identity in Render is: bhaidar-PC\test As you can see, the third line of output with the operating system thread identity shows that ASP.NET has restored the application impersonation identity on the thread. Although it is not shown in the output, the IPrincipal available from both Thread.CurrentPrincipal and the context’s User property correctly reflect the authenticated user in both the begin and end event handlers. Remember, though, that you cannot rely on the value of Thread.CurrentPrincipal in the asynchronous work itself for the reasons discussed earlier in the asynchronous pipeline section. EndRequest The EndRequest event is the last event in the unified request-processing pipeline. Once a request starts running in the pipeline, situations can occur that result in termination of the request. As a result, EndRequest is the only pipeline event that is guaranteed to occur after BeginRequest. Terminating a request usually results in bypassing all remaining pipeline events and going directly to EndRequest, with the exception introduced in ASP.NET 3.5, which is the LogRequest event that will also get fired even if an error occurred in the processing of the current request. Download at Boykma.Com 143 Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model If you remember the discussion of the AuthenticateRequest and AuthorizeRequest events, DefaultAuthenticationModule, FileAuthorizationModule, and UrlAuthorizationModule all have the capability to forward a request directly to the EndRequest event. Because EndRequest is guaranteed to always run, it is a convenient place in the pipeline to perform cleanup tasks or final processing that absolutely must run at the completion of a request. Aside from security-related processing, EndRequest is also used by other ASP.NET code such as the Session​ StateModule to ensure that session teardown and persistence always occur. For security purposes, the event is used by FormsAuthenticationModule to carry out custom actions when an unauthenticated user attempts to access a protected resource. The FormsAuthentication​ Module relies on the value of Response.StatusCode to determine whether any special end request processing is necessary. Because forms authentication is the most common authentication mode used for Internet-facing ASP.NET sites, we will concentrate on what the FormsAuthenticationModule does during this event. During AuthenticateRequest, the FormsAuthenticationModule is only concerned with verifying the forms authentication ticket and attaching a FormsIdentity to the current HttpContext. However, you know that the forms authentication feature supports the ability to automatically redirect unauthenticated users to a login page. FormsAuthenticationModule supports this functionality by checking the Response.StatusCode property for each request during EndRequest. If it sees that StatusCode is set to 401 (and, of course, if the authentication mode is set to forms), then the module fetches the currently configured redirect URL for logins and appends to it a query-string variable called ReturnUrl. This query-string variable is assigned the value of the currently requested path plus any query string variables associated with the current request. Then FormsAuthenticationModule issues a redirect to the browser telling it to navigate to the redirect URL. Although FormsAuthenticationModule itself never sets a 401 status code, you saw earlier that both FileAuthorizationModule and UrlAuthorizationModule will set a 401 status code if either module determines that the user for the current request does not have access to the requested resource. As an extremely simple example, if you author a page on a site that is configured with forms authentication and put the following code in the Load event: Response.StatusCode = 401; After the page completes, the browser is redirected to the forms authentication login page because of the 401. In a production application though you would use a custom HTTP module or hook one of the Authenticate events and set the StatusCode there instead. Summary On each ASP.NET request, there are four different security identities to be aware of: ❑❑ ❑❑ ❑❑ ❑❑ 144 The operating system thread identity. The request authenticated identity set by IIS. The IPrincipal available on Thread.CurrentPrincipal. The IPrincipal available from HttpContext.Current.User Download at Boykma.Com Chapter 3: HTTP Request Processing in IIS 7.0 Integrated Model If you are using Windows authentication in your ASP.NET application, then the impersonation token from IIS is used to create a WindowsIdentity for both the current thread and the current context. If the current request is an anonymous user, then the WindowsIdentity is just the value of WindowsIdentity​ .GetAnonymous. For authenticated users, the WindowsIdentity represents the authenticated user credentials from the IIS impersonation token. For applications running on a UNC share, the Windows​ Identity that is created represents either the anonymous user account configured in IIS or the credentials that were used to authenticate the user. If you are using forms authentication, though, the impersonation token set by IIS has no bearing on the security information set on the thread and the context. Instead, for authenticated users, the Form​sAuthenticationModule will create a GenericPrincipal containing a FormsIdentity and set this value on the current context’s User property. If no authentication module sets an IPrincipal on the current context’s user property, the hidden DefaultAuthenticationModule will create a GenericPrincipal with a username set to the empty string and set this value on the current context’s User property. This module is also responsible for synchronizing the value of the User property with Thread.CurrentPrincipal. The operating system thread identity starts out as the identity of the IIS6 worker process. However, if the ASP.NET application is running locally and is using client impersonation, then ASP.NET uses the IIS impersonation token to switch the operating system thread identity. If the application is running on a UNC share though, then the operating system thread identity is that of the explicit UNC credentials configured in IIS. If application impersonation is used (regardless of running on a UNC share), ASP.NET switches the operating system thread identity to match the credentials of the application impersonation account. After all of the security identity information is established, developers still need to be careful when dealing with asynchronous pipeline events and asynchronous page handling. The main thing to remember is that you need to pass any required security information over to the asynchronous tasks. Neither ASP.NET nor the .NET Framework will automatically propagate security identities to asynchronous tasks, though there are some .NET Framework classes that make it pretty easy to accomplish this. Furthermore, a new native module has been introduced in IIS 7.0 named RequestFiltering module that allows administrators and developers, at the IIS level, to block access to requests based on the file type extensions. The extensions can be existing ones like .asp, .aspx, .xml, and so forth, or new customized file extensions. It can be configured through the application’s web.config configuration file. In addition, this module gives a variety of handy features to manage the security of a request including a special section to define hidden segments, in such a way that if any of the listed hidden segments is found in the URL of a request, the request is automatically rejected by IIS runtime. Download at Boykma.Com 145 Download at Boykma.Com 4 A Matter of Trust The topics discussed so far have centered on various pieces of security information: encryption key material, security identities, authentication and authorization, and so on. They dealt with security decisions that were tied to some concept of identity. The security identity may have been that of the browser user, or it may have been the identity of the running process. A different aspect of ASP.NET security uses the .NET Framework code access security (CAS) functionality to secure the code that runs in an ASP.NET site. Although the concept of code having its own set of rights has been around since the first version of the .NET Framework, more often than not the actual use of CAS by developers has been limited. In large part, this has been due to the complexities of understanding just what CAS is as well as how to effectively use CAS with your code. ASP.NET 1.1 substantially reduced the learning curve with CAS by introducing the concept of ASP.NET trust levels. In essence, an ASP.NET trust level defines the set of rights that you are willing to grant to an application’s code. This chapter thoroughly reviews the concept of ASP.NET trust levels, as well as new features in ASP.NET 3.5 around enforcement of trust levels that have not changed since ASP.NET 2.0 You will learn about the following areas of ASP.NET trust levels: ❑❑ Configuring and working with ASP.NET trust levels. ❑❑ What an ASP.NET trust level looks like. ❑❑ How a trust level definition actually works. ❑❑ Creating your own custom trust levels. ❑❑ Details on frequently asked questions for trust level customizations. ❑❑ A review of all the permissions defined in ASP.NET trust policy files. ❑❑ Advanced topics on writing code for partial trust environments. Download at Boykma.Com Chapter 4: A Matter of Trust What Is an ASP.NET Trust Level? ASP.NET 1.1, ASP.NET 2.0, and ASP.NET 3.5 have the concept of trust levels. In a nutshell, a trust level is a declarative representation of security rules that defines the set of .NET Framework classes your ASP.NET code can call as well as a set of .NET Framework features that your ASP.NET code can use. The declarative representation of this information is called a trust policy file. Because a trust level is a declarative representation, you can view the definitions of trust levels by looking at the trust policy files on disk, and you can edit these files to suit your needs. When you configure an ASP.NET site with a specific trust level, the application is said to be running in XYZ trust (where XYZ is specific trust level). Much of the code that runs in an ASP.NET application and certainly all of the code you write in codebehind files is restricted by the rules defined for the current trust level. Note that ASP.NET trust levels apply to only ASP.NET applications. Console applications, NT services, Winforms, and other applications still rely on a developer understanding the .NET Framework CAS features. Currently, no other execution environments provide a developer-friendly CAS abstraction like ASP.NET trust levels do. The specific trust levels that ship with ASP.NET 1.1, ASP.NET 2.0, and ASP.NET 3.5 (no new trust levels were added in ASP.NET 3.5) are listed here from the most permissive to the most restrictive trust level: ❑❑ Full trust ❑❑ High trust ❑❑ Medium trust ❑❑ Low trust ❑❑ Minimal trust When trust levels were introduced in ASP.NET 1.1, the decision was made to default all ASP.NET applications to Full trust. Because many ASP.NET sites were already written with the 1.0 version of the framework, it was considered too much of a breaking change to default ASP.NET applications to a more restrictive trust level. In ASP.NET 3.5 this is also the case, with all ASP.NET 3.5 applications also defaulting to Full trust. As the name implies, Full trust code can use any class in the .NET Framework and perform any privileged operation available to managed code. However, I admit that this is a pretty theoretical description of Full trust. A much simpler way to think of Full trust is that your code can call any arbitrary Win32 API. For most IT developer shops this may not be a particularly big deal, especially because you could already call any Win32 API back in ASP days. However, the .NET Framework was supposed to bring a security sandbox to managed code developers, and arguably being able to call interesting Win32 APIs that do things like reformat disk drives does not seem like much of a security sandbox. The .NET Framework did introduce a very robust code access security framework that allowed developers to prevent managed code from doing things like reformatting hard drives; there was just the “minor” problem that you needed to get a PhD in what is definitely one of the more esoteric (though incredibly powerful) areas of the framework. As a result, ASP.NET 1.0 development left CAS usage up to the individual developer, with the result being that future versions of ASP.NET allow Full trust by default. Running an ASP.NET application in anything other than Full trust means that the application is running in partial trust, which simply means any piece of managed code (not just ASP.NET code) that has one or 148 Download at Boykma.Com Chapter 4: A Matter of Trust more CAS restrictions being enforced on it. In the case of ASP.NET, because all trust levels below Full trust enforce varying degrees of CAS restrictions, running applications in less than Full trust means these applications are partially trusted by the .NET Framework. As you will see throughout this chapter, partial trust applications are blocked from certain features of the .NET Framework. Moving an application from Full trust to High trust is actually a pretty big security move, because running High trust restricts an ASP.NET application to only the set of rights defined in the High trust policy file. The specifics of what is allowed for each trust level will be reviewed in detail in the next few sections, but for now an easy way to think of High trust is that it prevents your ASP.NET code from calling unmanaged Win32 APIs. If you are unable to apply any of the other information covered in this chapter, at least try to switch your Internet-facing ASP.NET applications from running in Full trust to running in High trust. Turning off access to unmanaged Win32 APIs reduces the potential for mischief and unexpected consequences in your applications. The next restrictive trust level is Medium trust. Think of Medium trust as the trust level that a shared hosting company would want to use. The ASP.NET team attempted to model the set of permissions in Medium trust to match the set of restrictions that an Internet hosting company would probably want enforced for each of their customers. In addition to the previous restriction on calling Win32 APIs, the Medium trust level restricts file I/O access for an ASP.NET application to only the files and folders that are located within the application’s directory structure. In a shared hosting environment with many customers, each of whom does not trust any of the other customers, the restrictions in Medium trust prevent a malicious user from attempting to surf around the host machine’s local hard drive. Low trust is appropriate for a read-only web server and for web servers running specialized no-code or low-code applications. The default set of permissions in Low trust allow only read access to the application’s directory structure. In addition, Low trust does not allow ASP.NET code to reach out across the network. For example, in Low trust an ASP.NET application cannot call a SQL Server or use the System.Net.HttpWebRequest class to make HTTP calls to other web servers. Overall, Low trust is appropriate for web servers with applications that can effectively run in a standalone mode without relying on any other external servers. It is also the recommended trust level for developers that implement no-code or low-code execution environments. For example, SharePoint is an example of an application environment that requires no .aspx pages or very few .aspx pages on the web server’s file system. Developers usually work within the SharePoint environment (which is effectively its own sandbox) and typically do not need to place many .aspx files directly onto the file system. SharePoint developers also work within the coding guidelines and restrictions enforced by the SharePoint runtime, which in turn sits on top of the ASP.NET runtime. SharePoint v3 (the current version) actually uses a modified variation of ASP.NET’s Minimal trust level known as WSS_Minimal. The WSS_Minimal is an ASP.NET custom trust level. The last ASP.NET trust level is Minimal trust. As its name implies, this trust level allows only the most minimal capabilities for an ASP.NET application. Other than running innocuous code (for example, a web-based calculator or basic .aspx pages), ASP.NET code running in Minimal trust cannot call into classes or attempt operations that could cause any type of security risk. This trust level is suitable for highly secure applications where 99 percent of any complex logic lives within compiled binaries that are deployed in the Global Assembly Cache (GAC). Because deploying a binary in the GAC requires administrative privileges, locking an ASP.NET web server down to Minimal trust effectively requires administrator intervention to deploy any code of consequence onto a web server. Download at Boykma.Com 149 Chapter 4: A Matter of Trust To summarize at a high level, the following table shows the ASP.NET trust levels and the general concept behind each trust level: Trust Level Full High Medium Low Minimal Used For Any and all code is allowed to run. Mainly intended for backwards compatibility with ASP.NET 1.0 and 1.1 applications that were not aware of how to use CAS or how to work with ASP.NET trust levels. Among other restrictions, ASP.NET code cannot call into unmanaged Win32 APIs. A good first step for securing Internet-facing ASP.NET applications. Intended as the default trust level for shared hosting environments where multiple untrusted customers use the same machine. Also recommended for any Internetfacing production applications. A set of permissions suitable for applications such as SharePoint that provide their own sandboxed execution environment. Also useful for read-only applications that don’t require network access to other backend servers. Locked down web servers that allow only the barebones minimum in your ASP.NET code. You will be able to add two numbers together and write out the results to a web page, but not much else. Configuring Trust Levels Now that you have a general idea of the target audience for each trust level, you need to know how to configure a trust level for your ASP.NET applications. The default of Full trust is defined in the root web.config file located in the CONFIG subdirectory of the framework installation directory: %windir%\Microsoft.NET\Framework\v2.0.50727\CONFIG\web.config At the top of the root web.config file is a location tag with a trust level definition that looks as follows: Changing the configuration element in the root web.config file affects all ASP.NET applications running on the machine. The element is conveniently located inside of a element to make it even easier for you to set the trust level for an entire machine, and then prevent anyone from changing the trust level on other web.config files. For example, if you make the following change to the location tag: 150 Download at Boykma.Com Chapter 4: A Matter of Trust then the individual applications that attempt to redefine the configuration element in their web.config files will end up with an exception. Because all configuration files located in the CONFIG directory are ACL’d to only allow the local Adminstrators group and SYSTEM write access, a malicious developer cannot use an ASP.NET application to make changes to machine.config or the root web.config file. Chapter 5 goes into more detail about how the configuration system in ASP.NET 3.5 can be used to prevent websites and web applications from changing machine wide settings. Although making changes to the root web.config file gives a machine administrator a great deal of leverage over the trust level setting for all applications on the machine, it is also likely that on some machines you will not be able to enforce a single trust level for all applications. The configuration element can also be defined in the web.config file for individual applications. This gives you the flexibility to pick and choose the appropriate trust level for different applications. However, allowing individual applications to change the trust level in their web.config files may not be something you want to allow for security reasons. As an alternative, you can define multiple tags in the root web.config using the syntax shown earlier, but with the addition of a path attribute that indicates which application the settings apply to. For example, the following configuration element defines the Medium trust level, but the setting applies only to a specific application, as opposed to all applications on the web server: Working with Different Trust Levels To give you a better idea of how trust levels affect an application, let’s use a sample application that attempts the following operations: ❑❑ Create an ADO (not ADO.NET) recordset using the primary interop assembly (PIA) that ships for ADO. ❑❑ Open Notepad.exe for read access. This file is located in the Windows directory. ❑❑ Connect to the Pubs database running on a local SQL Server. ❑❑ Open the application’s local web.config file for reading. ❑❑ Add two numbers together and output the results using a label control. The first operation is interesting because it uses the ADODB primary interop assembly (PIA) that provides a managed type wrapper around the older COM ADO objects. Calling into a PIA (or any managed code wrapper for a COM object) involves calling unmanaged code. As a result, running the following code will only work in Full trust. Download at Boykma.Com 151 Chapter 4: A Matter of Trust C# … using ADODB; … private void CreateRecordset() { RecordsetClass rc = new RecordsetClass(); int fieldCount = rc.Fields.Count; } protected void btnFull_Click(object sender, EventArgs e) { try { //Need to call a separate method so that the exception //occurs there, and can then be trapped from the click event. this.CreateRecordset(); lblResults.Text = “Successfully created an ADO recordset using the ADO PIA.”; } catch (Exception ex) { lblResults.Text = ex.Message + “
” + Server.HtmlEncode(ex.StackTrace); } } VB.NET … Imports ADODB … Private Sub CreateRecordset() Dim rc As New RecordsetClass() Dim fieldCount As Integer = rc.Fields.Count End Sub Protected Sub btnFull_Click( _ ByVal sender As Object, _ ByVal e As System.EventArgs) Handles btnFull.Click Try ‘Need to call a separate method so that the exception ‘occurs there, and can then be trapped from the click event. Me.CreateRecordset() lblResults.Text = “Successfully created an ADO recordset using the ADO PIA.” Catch ex As Exception lblResults.Text = ex.Message & “
” & _ Server.HtmlEncode(ex.StackTrace) End Try End Sub 152 Download at Boykma.Com Chapter 4: A Matter of Trust This sample code also requires that the website reference the ADO PIA from web.config, as follows: If you attempt to create an ADO object in less then Full trust, you receive an error message saying, “assembly does not allow partially trusted callers.” This is .NET Framework shorthand for saying that the application is running in something other than Full trust, and thus does not have rights to make calls into the ADO PIA. You should keep this scenario in mind if you migrate an ASP application to ASP.NET and then attempt to run the migrated ASP.NET application in anything other than Full trust. Older ASP applications usually depend on all sorts of COM objects, with ADO just being one of the most prevalent COM objects. Because calling COM objects from managed code always requires a managed-to-unmanaged code transition, migrated ASP applications can be a bit problematic to get running in partial trust. Although I discuss strategies that allow partially trusted applications to call into unmanaged code, migrated ASP applications are typically so dependent on COM objects that it can be expensive for developers to go through a converted application and implement workarounds just so the COM interop wrappers can be used in partial trust. The second piece of code attempts to open Notepad.exe for read access. Because Notepad.exe is located in the Windows directory, it clearly lies outside of the file and directory structure of the ASP.NET application. C# string filePath = “c:\\windows\\notepad.exe”; FileStream fs = File.OpenRead(filePath); fs.Close(); VB.NET Dim filePath As String = “c:\windows\notepad.exe” Dim fs As FileStream = File.OpenRead(filePath) fs.Close() This code will successfully run in Full and High trust, but at any other trust level it will result in a SecurityException, indicating that the request for a FileIOPermission failed. If you have applications that read and write data files located outside the directory structure of an ASP.NET application, High trust is realistically as low as you can go in terms of tightening trust levels without using the sandboxing approach described later in the chapter. You would need to move this type of code to a separate assembly and assert the necessary permissions in order to be able to read and write files outside the application’s directory structure in Medium or lower trust levels. Download at Boykma.Com 153 Chapter 4: A Matter of Trust The next piece of code uses System.Data.SqlClient to connect to a local database. C# string connString = @”server=.\SQL2005;database=pubs;Integrated Security=True;”; sqlConn = new SqlConnection(connString); sqlConn.Open(); VB.NET Dim connString As String = _ “server=.\SQL2005;database=pubs;Integrated Security=True;” sqlConn = New SqlConnection(connString) sqlConn.Open() At Medium trust or above, the code runs without a problem. However, Low and Minimal trust do not grant the necessary permissions to application code. As a result, Low or Minimal trust will result in a SecurityException, indicating that the request for a SqlClientPermission failed. The ability to connect to SQL Server is allowed in Medium trust because it is the trust level recommended for shared hosting machines. Because customers at Internet hosters usually want some type of database access, SqlClientPermission made sense to add to the Medium trust policy file. Opening files located within an application’s directory structure in read-only mode is allowed at Low trust or above. C# string filePath = Server.MapPath(“~”) + “\\web.config”; FileStream fs = File.OpenRead(filePath); fs.Close(); VB.NET Dim filePath As String = Server.MapPath(“~”) & “\web.config” Dim fs As FileStream = File.OpenRead(filePath) fs.Close() However, if you lower the trust level to Minimal trust, this code fails with a SecurityException indicating that the request for a FileIOPermission failed. Although these types of exceptions seem a bit unclear, it is intentional that the exception information and messages do not expose additional information. It can be a bit of a pain as a developer to track down what is happening, but the tradeoff is that additional information, such as specific file paths, or requested access modes, is not accidentally exposed in an error message that my be rendered in the browser. I will not show the last piece of sample code, because it is not terribly interesting to add two numbers together and output the results on a page. The point of the last sample code, though, is to prove that in Minimal trust you still have the ability to write some code in your ASP.NET pages. Basically, Minimal trust allows you to write code that depends only on the object instances available on the page and .NET Framework classes that operate entirely against data located in the application’s memory. However, any 154 Download at Boykma.Com Chapter 4: A Matter of Trust attempt to use .NET Framework classes that read and write files, communicate with databases and directory stores, reach out across the network, and so on results in some type of SecurityException. Anatomy of a Trust Level You have seen the general idea of how a trust level works. In the following sections, you get a better idea of how a trust level is defined, as well as the meaning of various security restrictions. The intent of the next few sections is to give you the information you need to be able to interpret the trust level policy files that ship with ASP.NET 3.5. Note, though, that the discussion intentionally tries to avoid diving too deep into the esoteric nature of how .NET Framework CAS works. Thankfully, the information you need to effectively use trust levels is much smaller than the knowledge required to become a CAS guru! Finding the Trust Policy File Medium trust is the default level recommended for hosters supporting untrusted customers. If you configure your server or application to run in Medium trust, ASP.NET must first determine just where the rules for Medium trust are located. Earlier you saw the configuration example for selecting a trust level, but some other configuration information was removed. The configuration that follows is what actually ships with the .Net Framework: The element contains the information ASP.NET needs to map a trust level name to a specific policy file location on disk. Furthermore, you have the option to define additional trust level names (in essence, additional trust levels) by adding your own configuration elements within the section. Any trust level defined in this section can be used as a value for the “level” attribute in the element. All locations defined in the preceding policyFile attributes are assumed to be relative to the following location: %windir%\Microsoft.NET\Framework\v2.0.50727\CONFIG If you create a custom trust level, the associated policy file must be placed in the CONFIG directory for ASP.NET to be able to use it. When you look in the CONFIG directory, you will actually see two copies of Download at Boykma.Com 155 Chapter 4: A Matter of Trust every policy file. For example, the medium trust policy file is defined in web_mediumtrust.config; a backup copy of the original medium trust policy file is defined in web_mediumtrust.config.default. Because you can edit the .config files to customize an individual trust policy, and because most of us will probably also do something wrong the first few times, the .default files are a handy way to get back to the original policy definitions. Needless to say, don’t edit the .default files, or at the very least, make a copy of them in a safe place! String Replacements in Policy Files After ASP.NET locates the appropriate policy file, it loads it into memory and performs some basic string replacements inside of it. If you open the medium trust policy file (web_mediumtrust.config) in a text editor, you will see the following string replacement tokens: ❑❑ $AppDir$ ❑❑ $AppDirUrl$ ❑❑ $CodeGen$ ❑❑ $OriginHost$ These replacement tokens exist primarily because the dynamic nature of ASP.NET applications makes it difficult to statically define all of the security information required to effectively use CAS. As you can probably infer from the first two string replacement tokens, because ASP.NET applications can be located anywhere on disk, ASP.NET needs a way to define permissions such that physical file paths can be flexibly defined. Both $AppDir$ and $AppDirUrl$ are representations of the physical file path for the application root. For example, if you create an application called MyApplication located within your wwwroot directory, and you are running off of the C drive, the string replacement tokens will have values of: ❑❑ $AppDir$ = c:\inetpub\wwwroot\MyApplication ❑❑ $AppDirUrl$ = file:///c:/inetpub/wwwroot/MyApplication Because different permission classes require different path representations, ASP.NET supports these two representations. The next replacement token, $CodeGen$, is used to represent the physical location on disk where all compiled code used by ASP.NET is located. As a side note, the term codegen is also shorthand in the ASP.NET world for any kind of auto-generated code artifacts that ASP.NET emits while running your application. Using the MyApplication example again, ASP.NET will create a directory structure that looks something like the following: %windir%\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\MyApplication \ e63333b8 This entire path, including the random hash value at the end (and there may actually be a few levels of these strange looking hash values) is used to create the value for $CodeGen$. The actual $CodeGen$ value is a file:/// URL-style representation of this physical path (just like the $AppDirUrl$ used previously). 156 Download at Boykma.Com Chapter 4: A Matter of Trust This location is important from a .NET Framework perspective because most of the executable assemblies for an ASP.NET application (both the assemblies you drop into the /bin directory and the ones ASP.NET auto-generates for pages, controls, and so on) are located somewhere within the directory tree represented by $CodeGen$. This set of code represents user code—the code that you, as a developer, have written. When running with any trust level other than Full trust, it is primarily user code that is restricted based on the security settings in the policy file. $CodeGen$ is the way ASP.NET can tell the .NET Framework where this user code exists. The last string replacement token, $OriginHost$, does not deal with file locations, but instead is used to allow developers to define either a specific URL or a URL pattern to be used with classes such as System.Net.HttpWebRequest. Some of the System.Net classes support CAS restrictions that allow you to define the set of URL endpoints that can be connected to using these classes. You can supply the value for $OriginHost$ by putting a value in the originUrl attribute of the element, as shown here: Defining Sets of Permissions A central concept to .NET Framework CAS is the idea of a permission set. Because code access security is all about applying a set of restrictions to one or more pieces of code, a permission set is a convenient way of grouping multiple restrictions into one logical definition—for example, a permission set. Because effective CAS usage typically requires varying levels of software restrictions within a single application, the .NET Framework supports the idea of naming individual permission sets so that developers can keep track of the intended use of the permission sets. Inside of the Medium trust policy file, ASP.NET defines the following named permission sets. ❑❑ FullTrust ❑❑ Nothing ❑❑ ASP.Net As the first named permission set implies, it defines a CAS policy that allows any kind of code or behavior in the .NET Framework. The definition for FullTrust in the policy file looks like: elements can contain child elements defining specific permissions. However, the FullTrust permission set clearly has no child elements. The reason this permission set allows managed code to pretty much do anything is because of the attribute definition: Unrestricted=”true”. This syntax indicates that any code that is granted the FullTrust permission set has unrestricted access to all functionality (including calling Win32 APIs and native code) in the .NET Framework. Download at Boykma.Com 157 Chapter 4: A Matter of Trust The next permission set, called Nothing, defines absolutely zero permissions, which, given the name, is what you would expect. The definition for Nothing in the policy file looks like this: Because the Nothing named permission set has no child elements, and no other attribute values of note, the permission set effectively defines an empty set of permissions. The last permission set is the most interesting one, because it is the ASP.NET named permission set that differs across the various policy files. The FullTrust and Nothing permission set definitions are the same in all of the policy files. However, it is the varying definitions of the ASP.NET permission set that gives each trust level its unique behavior. The partial definition for the ASP.NET named permission set is shown here: Because the ASP.NET permission set would be pretty useless without a set of defined permissions, it is the only named permission set with child elements defining a number of specific security rights for code. Defining Individual Permissions An individual permission in a policy file is defined with an element. The in-memory representation of many interesting .NET Framework CAS permissions are classes that derive from a class called CodeAccessPermission. Because the CodeAccessPermission class happens to implement the IPermission interface, the declarative representation of a CodeAccessPermission is an element. For example, the Medium trust policy file allows user code to make use of the System.Data.SqlClient classes. The definition of this permission looks like this: Because the System.Data.SqlClient classes do not support more granular permission definitions, the System.Data.SqlClient.SqlClientPermission is used to allow all access to the main functionality in the namespace, or deny access to this functionality. The previous definition sets the Unrestricted attribute to true, which indicates that user code in the ASP.NET application can use any functionality in System.Data.SqlClient that may demand this permission. 158 Download at Boykma.Com Chapter 4: A Matter of Trust Some permissions, though, have more complex representations. Usually, the permissions you will find in the ASP.NET policy files will support multiple attributes on an element, with the attributes corresponding to specific aspects of a customizable permission. For example, remember the earlier section describing string replacement tokens in policy files. The System.Security.Permissions .FileIOPermission is defined in the Medium trust policy file as follows: This permission supports a more extensive set of attributes for customizing security behavior. In this definition, the policy file is stating that user code in an ASP.NET application has rights to read and write files located within the application’s directory structure. Furthermore, user code in an ASP.NET application has rights to modify files (the Append attribute) and retrieve path information within the application’s directory structure. When ASP.NET first parses the policy file, it replaces $AppDir$ with the correct rooted path for the application. That way when the is deserialized by the .NET Framework into an actual instance of a FileIOPermission, the correct path information is used to initialize the permission class. Later in this chapter in the section titled “The Default Security Permissions Defined by ASP.NET,” you walk through the individual permissions that are used throughout the various policy files so that you get a better idea of the default CAS permissions. How Permission Sets Are Matched to Code At this point, you have a general understanding of permission sets and the individual permissions that make up a permission set. The next part of a policy file defines the rules that the .NET Framework uses to determine which permission sets apply to specific pieces of code. Clearly, CAS wouldn’t be very useful if, for example, all of the assemblies in the GAC were accidentally assigned the named permission set Nothing. So, there must be some way that the framework can associate the correct code with the correct set of permissions. The first piece of the puzzle involves the concept of code evidence, information about a piece of running code that meets the following criteria: ❑❑ The .NET Framework can discover, either by inferring it or by having the evidence explicitly associated with the code. Evidence includes things such as where an assembly is located and the digital signature (if any) of the assembly. ❑❑ The .NET Framework can interpret evidence and use it when making decisions about assigning a set of CAS restrictions to a piece of code. This type of logic is called a membership condition and is represented declaratively with the element. The unit of work that the .NET Framework initially uses as the basis for identifying code is the current stack frame. Essentially, each method that you write has a stack frame when the code actually runs (ignore compiler optimizations and such). At runtime, when a security demand occurs and the framework needs to determine the correct set of permissions to check against, the framework looks at Download at Boykma.Com 159 Chapter 4: A Matter of Trust the current stack frame. Based on the stack frame, the framework can backtrack and determine which assembly actually contains the code for that stack frame. And then backtracking farther, the framework can look at that assembly and start inferring various pieces of evidence about that assembly. Looking through the policy file, you will see a number of elements that make use of evidence. The elements are declarative representations of evidence-based comparisons used to associate security restrictions to code. I won’t delve into the inner workings of specific code group classes, because that is a topic suitable to an entire book devoted only to code access security. Generally speaking, though, a code group is associated with two concepts: ❑❑ A code group is always associated with a named permission set. Thus, the code group definitions in the ASP.NET policy files are each associated with one of the following named permission sets discussed earlier: ASP.Net, FullTrust, or Nothing. ❑❑ A code group defines a set of one or more conditions that must be met for the framework to consider a piece of code as being restricted to the named permission set associated with the code group. This is why elements are nested within elements. The definitions of membership conditions rely on the evidence that the framework determines about an assembly. The ASP.NET policy files define several elements, with some code groups nested inside of others. If you scan down the elements, though, a few specific definitions stand out. The very first definition is shown here: This definition effectively states the following: if no other code group definitions in the policy file happen to match the currently running code, then associate the code with the named permission set called “Nothing.” In other words, if some piece of unrecognized code attempts to run, it will fail because the “Nothing” permission set is empty. Continuing down the policy file, the next two code group definitions are very important. These two definitions are where the proverbial rubber hits the road when it comes to the ASP.NET trust feature. The $AppDirUrl$ token in the first membership condition indicates that any code loaded from the file directory structure of the current ASP.NET application should be restricted to the permissions defined in the ASP.NET named permission set. Also notice that the “Url” attribute ends with a /*, which ensures that any code loaded at or below the root of the ASP.NET application will be restricted by the ASP.NET permission set. Similarly, the second code group definition restricts any code loaded from the code generation directory for the ASP.NET application to the permissions defined in the ASP.NET named permission set. As with the first code group, the membership condition also ends in a /* to ensure that all assemblies loaded from anywhere within the temporary directory structure used for the application’s codegen will be restricted to the ASP.NET permission set. It is this pair of definitions that associates the ASP.NET named permission set to all the code that you author in your ASP.NET applications. The pair of definitions also restricts any of the code you drop into the /bin directory because of course that lies within the directory structure of an ASP.NET application. These two definitions are also why trust-level customizations (discussed a little later in this chapter) can be easily made to the ASP.NET named permission set without you needing to worry about any of the other esoteric details necessary to define and enforce CAS. The remaining elements in the policy files define a number of default rules, with the most important one being the following definition: This definition states that any code that is deployed in the GAC is assigned the FullTrust named permission set. This permission set allows managed code to make use of all the features available in the .NET Framework. Because you can author code and deploy assemblies in the GAC, you have the ability to create an ASP.NET application with two different levels of security restrictions. User code that lives within the directory structure of the ASP.NET application will be subjected to the ASP.NET permission set, but any code that you deploy in the GAC will have the freedom to do whatever it needs to. This concept of full trust GAC assemblies will come up again in the section “Advanced Topics on Partial Trust” where there is a discussion of strategies for sandboxing privileged code. Download at Boykma.Com 161 Chapter 4: A Matter of Trust Other Places that Define Code Access Security Although the previous topics focused on how ASP.NET defines the permission set associations using a trust policy file, the .NET Framework defines a more extensive hierarchy of code access security settings. Using the .NET Framework 2.0 Configuration MMC, you can create security policies for any of the following: ❑❑ Enterprise ❑❑ Machine ❑❑ User This means that you can create declarative representations of permissions, permission sets, and code groups beyond those defined in the ASP.NET trust policy file. If your organization defines security policies at any of these levels, it is possible that the permissions defined in the ASP.NET trust policy file may not exactly match the behavior exhibited by your application. This occurs because each successive level of security policy (with the lowest level being the ASP.NET trust policy) acts sort of like a filter. Only security rights allowed across all of the levels will ultimately be granted to your code. With that said, though, in practice many organizations are either unaware of the security configuration levels, or have considered them too complicated to deal with. That is why ASP.NET trust policies with their relatively easy-to-understand representations are ideally suited for quickly and easily enforcing CAS restrictions on all of your web applications. By default, the .NET Framework defines only restrictive CAS policies for the Machine level. The framework defines a number of different code groups that divvy up code based on where the code was loaded from. These code group definitions depend on the concept of security zones that you are probably familiar with from Internet Explorer. You might wonder why ASP.NET needs to define its own concept of CAS with trust levels when zone-based CAS restrictions are already defined and used by the Framework. ASP.NET cannot really depend on the default Machine level CAS definitions because, for all practical purposes, ASP.NET code always runs locally. The ASP.NET pages exist on the local hard drive of the web server, as does the Temporary ASP.NET Files directory. Even in when running from a UNC share, most of the actual compiled code in an application is either auto-generated by ASP.NET or shadow copied into the local Temporary ASP.NET Files directory. As a result, if ASP.NET didn’t use trust levels, all ASP.NET code that you write would fall into the code group called My_Computer_Zone. The membership condition for this code group is the My Computer zone, which includes all code installed locally. Because the code group grants full trust to any assemblies that are installed locally, this means in the absence of ASP.NET trust levels, all ASP.NET code runs at full trust. This is precisely the outcome in ASP.NET 1.0, which predated the introduction of ASP.NET trust levels. A Second Look at a Trust Level in Action Earlier you saw an example of using various pieces of code in different trust levels and the failures that occurred. Now that you have a more complete picture of what exists inside of a trust policy file, reviewing how trust levels and CAS all hang together is helpful. Figure 4-1 outlines a number of important steps. 162 Download at Boykma.Com Chapter 4: A Matter of Trust (0) Application domain CAS policy established when the application domain started SecurityException is thrown ! (4b) If check fails User code stack frame Page code that uses System.Data.SqlClient (3) Framework checks appdomain CAS policy (2) Permission demand (1) Calls into System.Data.SqlClient classes demand SqlClientPermission ADO.NET continues and runs the requested method Figure 4-1 (4a) If check succeeds Step 0: Application Domain Policy As part of ASP.NET’s application domain initialization process, ASP.NET reads configuration to determine the appropriate trust policy that should be loaded from the CONFIG directory. When the file is loaded, and the string replacement tokens are processed, ASP.NET calls System.AppDomain.SetAppDomainPolicy to indicate that permissions defined in the trust level’s policy file are the CAS rules for the application domain. If your organization also defines CAS rules for the Enterprise, Machine, or User levels, then the application domain policy is intersected with all of the other predefined CAS rules. Step 1: User Code Calls into a Protected Framework Class One of the pieces of code from the sample application shown in the beginning of the chapter attempted to call into ADO.NET: C# string connString = @”server=.\SQL2005;database=pubs;Integrated Security=True;”; sqlConn = new SqlConnection(connString); sqlConn.Open(); Download at Boykma.Com 163 Chapter 4: A Matter of Trust VB.NET Dim connString As String = _ “server=.\SQL2005;database=pubs;Integrated Security=True;” sqlConn = New SqlConnection(connString) sqlConn.Open() Attempting to open a connection or run a command using the System.Data.SqlClient’s classes results in a demand being made in ADO.NET for the SqlClientPermission. ADO.NET makes the demand by having the framework construct an instance of the SqlClientPermission class and then calling the Demand method on it. Step 2: The Demand Flows up the Stack The technical details of precisely how the Framework checks for a demanded permission are not something you need to delve into. Conceptually, though, demanding a permission causes the Framework to look up the call stack at all of the code that was running up to the point that the permission demand occurred. Underneath the hood, the Framework has a whole set of performance optimizations so that in reality the code that enforces permission demands doesn’t have to riffle through every last byte in what could potentially be a very lengthy call stack. Ultimately, though, the Framework recognizes the user code from the sample page, and it decides to check the set of permissions associated with the page. Step 3: Checking the Current CAS Policy This is where the effects of the ASP.NET trust policy come into play. Because ASP.NET earlier initialized a set of permissions (code groups and membership conditions for the application domain) the Framework now has a set of rules that it can reference. If the user code sits on an ASP.NET page, the Framework uses the UrlMembershipCondition definitions defined earlier in the trust policy file to determine the permissions associated with the page code. The page code at this point has actually been compiled into a page assembly (either automatically or from an earlier precompilation), and this assembly is sitting somewhere in the Temporary ASP.NET Files directory structure for the current application. Because the permissions for files located in the codegen directory are the ones from the ASP.NET named permission set, the Framework looks for the existence of SqlClientPermission in that permission set. Step 4: The Results of the Check If the ASP.NET application is running at Medium trust or above, the Framework will find the SqlClientPermission in the permission set associated with user code. In this case, the Framework determines that the user code passes the security check, and as a result the original ADO.NET call is allowed to proceed. What isn’t shown in Figure 4-1 is the extended call stack that sits on top of the code sitting in the .aspx page. When the Framework determines that the user code has the necessary permissions, it continues up the call stack, checking every assembly that is participating on the current thread. In the case of ASP.NET, though, all code prior to the button click event handler calling ADO. NET is code that exists in System.Web.dll or some other .NET Framework assembly. Because all these assemblies exists in the GAC, and GAC’d assemblies have full trust, all of the other code on the class stack is considered to implicitly have all possible permissions. On the other hand, if the ASP.NET application is running in Low or Minimal trust, the .NET Framework will not find a SqlClientPermission for the page’s code, and the permission demand fails with a stack that looks roughly like: 164 Download at Boykma.Com Chapter 4: A Matter of Trust Request for the permission of type ‘System.Data.SqlClient.SqlClientPermission, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089’ failed. at System.Security.CodeAccessSecurityEngine.Check(Object demand, StackCrawlMark& stackMark, Boolean isPermSet) at System.Security.PermissionSet.Demand() at The downside of CAS is that when a security exception occurs, it usually results in semi-intelligible results like those shown previously. However, when you encounter a security exception (and it is usually an instance of System.Security. SecurityException that is thrown), with a little probing you can usually pick apart the call stack to get some idea of what happened. For the previous example, you can see that the bottom of the call stack is the button click handler; that immediately tells you the user code triggered the call that eventually failed. Moving up the call stack a bit, System.Data.SqlClient.SqlConnection.PermissionDemand() gives you an idea of which System.Data.SqlClient class your code is calling. Moving up the stack a bit more, you see various calls into System.Security.CodeAccessSecurity​ Engine. This class is part of the internal guts of the CAS enforcement capability in the .NET Framework. Finally, at the top of the stack trace is the information pertaining to the specific permission request that failed, which in this case is SqlClientPermission. In this example, the SqlClientPermission is a very simple permission class that represents a binary condition: either code has rights to call into System.Data.SqlClient, or it doesn’t. As a result, you don’t need additional information to investigate the problem. So, troubleshooting this problem boils down to figuring out why the code in the button click event doesn’t have rights to call into various ADO.NET classes. With an understanding of ASP.NET trust levels in mind, the first thing you would do is determine the current trust level. In this case, I set the application to run in Minimal trust. In the policy file for Minimal trust, SqlClientPermission has not been granted to ASP.NET code. Troubleshooting More Complex Permissions Although troubleshooting SqlClientPermission is pretty simple, other more complex permission types are not so easy. For example, the System.Security.Permissions.FileIOPermission class supports much more granular permission definitions. As you saw earlier in some snippets from the ASP.NET trust policy files, you can selectively grant access to read files, create files, modify existing files, and so on. Using the sample application from the beginning of the chapter again, you can attempt to read a file running in Minimal trust: C# string filePath = Server.MapPath(“~”) + “\\web.config”; FileStream fs = File.OpenRead(filePath); fs.Close(); VB.NET Dim filePath As String = Server.MapPath(“~”) & “\web.config” Dim fs As FileStream = File.OpenRead(filePath) fs.Close() Download at Boykma.Com 165 Chapter 4: A Matter of Trust This code results in the following stack trace: Request for the permission of type ‘System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089’ failed. at System.Security.CodeAccessSecurityEngine.Check(Object demand, StackCrawlMark& stackMark, Boolean isPermSet) at System.Security.CodeAccessPermission.Demand() at System.Web.HttpRequest.MapPath(VirtualPath virtualPath, VirtualPath baseVirtualDir, Boolean allowCrossAppMapping) at System.Web.HttpServerUtility.MapPath(String path) at _Default.btnLow_Click(Object sender, EventArgs e) Unfortunately, from this stack trace, you can glean only that some piece of user code (the click event handler at the bottom of the trace) triggered a call to System.Web.HttpRequest.MapPath and that this call eventually resulted in a SecurityException because the check for FileIOPermission failed. The information about the FileIOPermission failure, though, says absolutely nothing about why it failed. At this point, about the only thing you can do is sleuth around the rest of the stack trace and attempt to infer what kind of FileIOPermission check failed. Was it read access, write access, or what? In this case, the call to MapPath gives you a clue because ASP.NET has a MapPath method on the HttpServerUtility class. Because the purpose of MapPath is to return the physical file path representation for a given virtual path, you have a clue that suggests something went wrong when attempting to discover the physical file path. Because the application is running at Minimal trust, you know that there are no FileIOPermission definitions inside of the Minimal trust policy file. With the information about MapPath, you can make a reasonable guess that if you wanted the code in the click event handler to succeed, you would at least need to create a declarative for a FileIOPermission that granted PathDiscovery to the application’s physical directory structure. One of the other samples attempts to open a file outside of the directory structure of the application while running in Medium trust. Doing so still fails with a SecurityException complaining about the lack of a FileIOPermission. However, this time the stack trace includes the following snippet: Snip… at System.Security.CodeAccessPermission.Demand() at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy) at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share) Snip… Now the stack trace looks a bit more interesting. The snippet shows that one type of file I/O operation was attempted and during initialization of the FileStream, a demand occurred. Because the failure involved FileIOPermission, you have enough information in the stack trace to realize that you need to look at the code that opened the file stream. Depending on the location of the requested file, as well as the type of access requested, you can look in the trust policy file (Medium trust in this case) and see which file permissions are granted by default. In this case, because only file I/O permissions within the 166 Download at Boykma.Com Chapter 4: A Matter of Trust scope of the application’s directory structure are granted, and the code is attempting to open a file in the %windir% directory, you need to grant extra permissions. Adding the following permission element allows the application to open notepad.exe even though the application is running in Medium trust: Troubleshooting permission failures and the need to edit policy files to fix the failures leads us to the next topic. Creating a Custom Trust Level At some point, you may need to edit the permissions in a trust policy file and create a custom trust level. Creating a custom trust level involves the following tasks: 1. Creating a policy file containing your updated permission definitions 2. Determining the declarative representation of the new permissions 3. Applying the new trust level to your application Creating a Policy File Although you can edit the existing policy files located in the CONFIG directory, unless you are making minor edits for an existing trust level, you should create a separate policy file that represents the new custom set of permissions you are defining. Start with the policy file that has the closest set of permissions to those you want to define. This discussion starts with the Medium trust policy file. I made a copy of the Medium trust policy file and called it web_mediumtrust_custom.config. After you have a separate copy of the policy file, you need to edit some configuration settings so that a trust level is associated with the policy file. Hooking up the policy file up so that it is available for use requires editing the root web.config file located in the framework’s CONFIG subdirectory. Remember earlier that you looked at the configuration element. Creating the following entry inside of the element makes the custom policy file available for use as a custom trust level: Now ASP.NET applications that need the set of permissions defined inside of web_mediumtrust_custom​ .config can simply reference the Medium_Custom trust level. Download at Boykma.Com 167 Chapter 4: A Matter of Trust Determining Declarative Permission Representations So far you have been looking at preexisting permission definitions. However, these declarative representations must have come from somewhere and must follow some type of expected schema; otherwise, it would be a free-for-all when class implementers tried to determine the correct definitions for a permission. Two pieces of information are necessary for enabling new permissions in a policy file: ❑❑ The class information for the security permission class ❑❑ The declarative XML representation of the permission Determining the class information for a new permission is pretty simple. Usually you know what piece of code you are attempting to enable in a partial trust application, so you know the calls that are being made and that are failing. The first example of creating a new custom permission attempts to enable OleDb for use in Medium trust. You can determine the permission that is necessary to enable usage of the classes in System​ .Data.OleDb by first attempting to run a page that uses OleDb in Medium trust and looking at the failure information. The following code initially does not work in Medium trust because the policy file for Medium trust only grants the SqlClientPermission: C# OleDbConnection oc = new OleDbConnection(“Provider=SQLOLEDB;” + @”Data Source=.\SQL2005;Initial Catalog=pubs;” + “Integrated Security=True;Connect Timeout=30”); oc.Open(); OleDbCommand ocmd = new OleDbCommand(“select * from authors”, oc); OleDbDataReader or = ocmd.ExecuteReader(); VB.NET Dim oc As New OleDbConnection(“Provider=SQLOLEDB;” & _ ControlChars.CrLf & _ “Data Source=.\SQL2005;Initial Catalog=Pubs;” _ & ControlChars.CrLf & “Integrated Security=SSPI;”) oc.Open() Dim ocmd As New OleDbCommand(“select * from authors”, oc) Dim orr As OleDbDataReader = ocmd.ExecuteReader() Running the code results in the following exception information: [SecurityException: Request for the permission of type ‘System.Data.OleDb. OleDbPermission, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a 5c561934e089’ failed.] How convenient! The first piece of information is right there in the exception information. Using elements in a trust policy file requires that you first register the type of the permission class that you are defining. This is necessary because the IPermission interface is a generic representation of a code-access permission, but you are attempting to define very specific permissions, 168 Download at Boykma.Com Chapter 4: A Matter of Trust sometimes with additional attributes or nested permissions that are unique to the specific class of permission you are working with. You can register the OleDbPermission type in your custom policy file by copying the information out of the exception dump, and into a element, as shown here: The Name attribute can actually be set to any string value because it is used by individual elements to reference the correct permission type. However, you would normally use the class name without other type or namespace information as the value for the Name attribute. The Description attribute is set to a type string that the .NET Framework uses to resolve the correct permission type at runtime. In the previous example, the Descrption attribute has been set to the strong type definition that is conveniently available from the exception text. Now that the permission class information has been entered into the policy file, the next step is to determine the declarative representation of an OleDbPermission. The easiest way to do this in the absence of any documentation for a XML representation as follows: C# using System.Data.OleDb; using System.Security; using System.Security.Permissions; … protected void Page_Load(object sender, EventArgs e) { OleDbPermission odp = new OleDbPermission(PermissionState.Unrestricted); SecurityElement se = odp.ToXml(); Response.Write(Server.HtmlEncode(se.ToString())); } VB.NET Imports System.Data.OleDb Imports System.Configuration Imports System.Collections Imports System.Security Imports System.Security.Permissions … Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles Me.Load Download at Boykma.Com 169 Chapter 4: A Matter of Trust Dim odp As New OleDbPermission(PermissionState.Unrestricted) Dim se As SecurityElement = odp.ToXml() Response.Write(Server.HtmlEncode(se.ToString())) End Sub The sample code constructs an instance of the permission class, passing it a value from the System​ .Security.Permissions.PermissionState enumeration. The sample code essentially creates a permission that grants unrestricted permission to the full functionality of the System.Data.OleDb namespace. The XML representation of the permission is created by calling ToXML() on the permission, which results in an instance of a System.Security.SecurityElement. A SecurityElement is the programmatic representation of the XML for a permission. You can get the string representation of the XML by calling ToString() on the SecurityElement. The end result of running this code is the declarative representation of an OleDbPermission instance: This representation is almost exactly what you need to drop into your custom policy file, with one minor change. Because you already defined a earlier for the OleDbPermission type, the lengthy type definition isn’t required. Instead, you want to enter the following XML into your custom policy file: The class attribute will be interpreted as a reference to a permission class that is keyed by the name OleDbPermission. Because you created a earlier named OleDbPermission, at runtime the Framework will correctly infer that the definition here is for an instance of the type defined by the OleDbPermission security class. You can place the declaration anywhere within the list of elements that are nested underneath the element for the ASP.NET named permission set. The following XML shows where to place the OleDbPermission declaration: At this point, the edits to the policy file are complete, and the only task left is to associate the sample application with the custom trust level defined by this policy file. Applying the New Trust Level Earlier, you defined a new trust level called Medium_Custom for the modified policy file. The sample ASP.NET application can use this trust level by redefining the trust level in its web.config: With the creation of the custom trust policy file and the use of the custom trust level, when you run the sample code shown earlier, the application is able to open an OleDb connection and make a query against the pubs database. Additional Trust Level Customizations You have seen how to enable unrestricted OleDb permissions for an ASP.NET application. However, permission classes sometimes allow for more extensive customizations. In this section, you will take a look at a few of the more common (or more confusing!) permissions classes you may encounter Customizing OleDbPermission The OleDbPermission class allows more than just a simple binary decision on class usage. For example, hosters frequently want to enable Access (aka Jet) databases for their customers, but at the same time they don’t want to throw the doors wide open to any kind of OleDb drivers being used. For example, let’s say you wanted to allow use of only the System.Data.OleDb classes with the following restrictions: ❑❑ Only Access could be used through OleDb. Any other data provider, including OleDb-based SQL Server access, is disallowed. ❑❑ To prevent any type of extended information from being passed on the connection string, you allow only customers to set the database location, username, and password. You can model this set of restrictions in code using the OleDbPermission class, as shown here: C# OleDbPermission odp = new OleDbPermission(PermissionState.None); odp.Add(“Provider=Microsoft.Jet.OLEDB.4.0”, “data source=;user id=;password=;”, KeyRestrictionBehavior.AllowOnly); SecurityElement se = odp.ToXml(); Response.Write(Server.HtmlEncode(se.ToString())); Download at Boykma.Com 171 Chapter 4: A Matter of Trust VB.NET Dim odp As New OleDbPermission(PermissionState.Unrestricted) odp.Add(“Provider=Microsoft.Jet.OLEDB.4.0”, _ “data source=;user id=;password=;”, _ KeyRestrictionBehavior.AllowOnly) Dim se As SecurityElement = odp.ToXml() Response.Write(Server.HtmlEncode(se.ToString())) Unlike the first example of using OleDbPermission, this code uses the Add method to selectively add the set of allowed connection strings that can be used with System.Data.OleDb. The Add method in the previous code says that connection strings that reference the Jet provider are allowed. Allowable connection strings can be further modified with the data source, user id, and password attributes. Attempts to create an OleDbConnection with a connection string that does not follow these constraints will result in a SecurityException. Writing out the XML representation of the permission, and modifying the class attribute as mentioned earlier results in the following declarative syntax that can be placed in a custom policy file: Notice how you now have a element that itself contains nested security information. Permission classes are free to define whatever XML representation they require and this additional information can be nested within . This allows permission classes to manage collections of security information, rather than being restricted to a single static definition of one security rule. In the case of OleDbPermission, this enables you to define as many connection string constraints as you need, although this example defines only the single constraint. If you run the sample code shown earlier that connects to SQL Server, a security exception is thrown. However, if instead you attempt to connect to an MDB database, as the following example shows, everything works: C# //Using a Sql connection string at this point will result in a SecurityException OleDbConnection oc = new OleDbConnection( “Provider=Microsoft.Jet.OLEDB.4.0;” + @”data source=C:\inetpub\wwwroot\379301_code\379301 + @ch04_code\cs\TrustLevels\\ASPNetdb_Template.mdb;”); oc.Open(); OleDbCommand ocmd = new OleDbCommand(“select * from aspnet_Applications”, oc); OleDbDataReader or = ocmd.ExecuteReader(); 172 Download at Boykma.Com Chapter 4: A Matter of Trust VB.NET Dim oc As New OleDbConnection( _ “Provider=Microsoft.Jet.OLEDB.4.0;” & _ “data source=C:\inetpub\wwwroot\379301_code\379301 ch04_code\” & _ “cs\TrustLevels\\ASPNetdb_Template.mdb;”) oc.Open() Dim ocmd As New OleDbCommand(“select * from aspnet_Applications”, oc) Dim orr As OleDbDataReader = ocmd.ExecuteReader() If a hoster provisioned only a specific database name (or names), you could even go one step further and define the in the custom policy file to restrict access to a predefined name: Notice how the ConnectionString attribute in the element now also includes the data source definition. Furthermore, KeyRestrictions no longer allows you to specify a custom value for data source. Because ASP.NET performs a string search-and-replace for all tokens in a trust policy file, you can use the replacement token $AppDir$ inside of the ConnectionString attribute. The previous definition has the net effect of restricting an ASP.NET application to using only an Access database called ASPNetdb_Template.mdb located in the root of the application’s physical directory structure. Attempting to use any other Access MDB will result in a SecurityException. Customizing OdbcPermission Another data access technology that many folks use in ASP.NET is ODBC. Even though it probably seems a bit old-fashioned to still be using ODBC (as I like to half-joke: every few years Microsoft needs to release an entirely new data access technology due to our predilection for reorgs), it is still widely used due to the prevalence of ODBC drivers that have been around for years. In many cases, database back ends that are no longer actively supported are accessible only through proprietary APIs or custom ODBC drivers. Another reason ODBC can be found on ASP.NET servers is that customers using the open-source MySQL database used to need the MySQL ODBC driver, although recently a .NET driver for MySQL was released. If you want to enable ODBC for your ASP.NET applications, you can follow the same process shown earlier for OleDb. A element needs to be added to the custom policy file that registers the OdbcPermission class: Download at Boykma.Com 173 Chapter 4: A Matter of Trust Next, you need to determine what the declarative representation of an OdbcPermission looks like. Modifying the OleDb sample code used earlier, the following snippet outputs the XML representation of a permission that allows only the use of the Access provider via the System.Data.Odbc classes: C# OdbcPermission odp = new OdbcPermission(PermissionState.None); odp.Add(“Driver={Microsoft Access Driver (*.mdb)};”, “Dbq=;uid=;pwd=;”, KeyRestrictionBehavior.AllowOnly); SecurityElement se = odp.ToXml(); Response.Write(Server.HtmlEncode(se.ToString())); VB.NET Dim odp As New OdbcPermission(PermissionState.None) odp.Add(“Driver={Microsoft Access Driver (*.mdb)};”, _ “Dbq=;uid=;pwd=;”, KeyRestrictionBehavior.AllowOnly) Dim se As SecurityElement = odp.ToXml() Response.Write(Server.HtmlEncode(se.ToString())) The OdbcPermission class actually has a programming model that is very similar to the OleDb Permission class. You can add multiple connection string related permissions into a single instance of OdbcPermission. Running the previous code, and then tweaking the output to use the shorter reference in the class attribute, results in the following declaration: Although the syntax of the connection string text is a bit different to reflect the ODBC syntax, you can see that the permission declaration mirrors what was shown earlier for OleDb. With this permission added to the custom trust policy file, the code that uses Access will run without triggering any security exceptions. C# //The following won’t work when only Access connection strings are allowed in the //trust policy file. //OdbcConnection oc = // new OdbcConnection(“Driver={SQL Server};” + // “Server=foo;Database=pubs;Uid=sa;Pwd=blank;”); 174 Download at Boykma.Com Chapter 4: A Matter of Trust OdbcConnection oc = new OdbcConnection( “Driver={Microsoft Access Driver (*.mdb)};” + @”Dbq=C:\inetpub\wwwroot\379301_code\379301 ch04_code\cs\” + @TrustLevels\\ASPNetdb_Template.mdb;”); oc.Open(); OdbcCommand ocmd = new OdbcCommand(“select * from aspnet_Applications”, oc); OdbcDataReader or = ocmd.ExecuteReader(); VB.NET ‘The following won’t work when only Access ‘conn strings are allowed for ODBC ‘OdbcConnection oc = ‘ new OdbcConnection(“Driver={SQL Server};Server=foo; Database=pubs;Uid=sa;Pwd=blank;”); Dim oc As New OdbcConnection( _ “Driver={Microsoft Access Driver (*.mdb)};” & _ “Dbq=C:\inetpub\wwwroot\379301_code\379301 ch04_code\cs” & _ “\TrustLevels\\ASPNetdb_Template.mdb;”) oc.Open() Dim ocmd As New OdbcCommand(“select * from aspnet_Applications”, oc) Dim orr As OdbcDataReader = ocmd.ExecuteReader() However, attempting to create an OdbcConnection with a SQL Server-style connection string results in a SecurityException because it is disallowed by the permission definition in the trust policy file. Allowing ODBC and OLEDB in ASP.NET Now that you have seen how to enable ODBC and OleDb inside of partial trust ASP. NET applications, you should be aware that running either of these technologies reduces the security for your web applications. Many drivers written for ODBC and OleDb predate ASP.NET and for that matter predated widespread use of the Internet in some cases. The designs for these drivers didn’t take into account scenarios such as shared hosters selling server space to customers on the Internet. For example, the Jet provider for Access can be used to open Excel files and other Office data formats in addition to regular MDB files. Because many Office files, including Access databases, support scripting languages like VBScript, it is entirely possible for someone to use an Access database as a tunnel of sorts to the unmanaged code world. If you lockdown an ASP.NET application to partial trust but still grant selective access with the OleDbPermission, developers can write code to open an arbitrary Access database. After that happens, a developer can issue commands against the database that in turn trigger calls into VBScript or to operating system commands and of course when that happens, you are basically running the equivalent of an ASP page with the capability to call arbitrary COM objects. Continued Download at Boykma.Com 175 Chapter 4: A Matter of Trust Because the .NET Framework CAS system does not extend into the code that runs inside of an Access database, after the OleDbPermission demand occurs, the Framework is no longer in the picture. In the case of Access, the Jet engine supports Registry settings that enable a sandboxed mode of operation. The sandbox prevents arbitrary code from being executed as the side effect from running a query. There may be additional avenues, though, for running scripts in Access databases. (I admit to having little experience in Access, which is probably a good thing!) Overall, the general advice is to thoroughly research the vagaries of whatever ODBC or OleDb drivers you are supporting and as much as possible, implement the mitigations suggested by the various vendors. Using the WebPermision One of the permissions defined in the Medium and High trust files is for the System.Net.Web Permission. This is probably one of the most confusing permissions for developers to use due to the interaction between the element and the settings for this permission. The default declaration looks like this: As with some of the other permissions you have looked at, the WebPermission supports multiple sets of nested information. Although a WebPermission can be used to define both outbound and inbound connection permissions, normally, you use WebPermission to define one or more network endpoints that your code can connect to. The default declaration shown previously defines a single connection permission that allows partially trusted code the right to make a connection to the network address defined by the element. However, the definition for this element has the string replacement token: $OriginHost$. This definition is used conjunction with the element, which includes an attribute called originHost and its value is used as the replacement value for $OriginHost$. For example, if you define the following element: . . . when ASP.NET processes the trust policy file, it will result in a permission that grants connect access to http://www.microsoft.com/. Although the attribute is called originUrl, the reality is that the value you put in this attribute does not have to be your web server’s domain name or host name. You can set a value that corresponds to your web farm’s domain name if, for example, you make web service calls to other machines in your environment. However, you can just as easily use a value that points at any arbitrary network endpoint as was just shown. One subtle and extremely frustrating behavior to note here is that you need to have a trailing / at the end of the network address defined in the originUrl attribute. Also, when you write code that actually uses System.Net classes to connect to this endpoint, you also need to remember to use a trailing / character. 176 Download at Boykma.Com Chapter 4: A Matter of Trust With the level setting shown previously, the following code allows you to make an HTTP request to the Microsoft home page and process the response: C# HttpWebRequest wr = (HttpWebRequest)WebRequest.Create(“http://www.microsoft.com/”); HttpWebResponse resp = (HttpWebResponse)wr.GetResponse(); Response.Write(resp.Headers.ToString()); VB.NET Dim wr As HttpWebRequest = CType( _ WebRequest.Create(“http://www.microsoft.com/”), _ HttpWebRequest) Dim resp As HttpWebResponse = CType(wr.GetResponse(), HttpWebResponse) Response.Write(resp.Headers.ToString()) Because the WebPermission class also supports regular expression based definitions of network endpoints, you can define originUrl using a regular expression. The reason regular expression-based URLs are useful is that the WebPermission class is very precise in terms of what it allows. Defining a permission that allows access to only www.microsoft.com means that your code can access only that specific URL. If you happened to be curious about new games coming out, and created an HttpWebRequest for www.microsoft.com/games/default.aspx, then a SecurityException occurs. You can rectify this by instead defining originUrl to allow requests to any arbitrary page located underneath www.microsoft.com. Notice the trailing .* at the end of the originUrl attribute. Now the System.Net.WebPermission class will interpret the URL as a regular expression; the trailing .* allows any characters to occur after the trailing slash. With that change, the following code will work without throwing any security exceptions: C# HttpWebRequest wr = (HttpWebRequest)WebRequest.Create(“http://www.microsoft.com/games/default.aspx”); VB.NET Dim wr As HttpWebRequest = CType( _ WebRequest.Create(“http://www.microsoft.com/games/default.aspx”), _ HttpWebRequest) Although the examples shown all exercise the HttpWebRequest class directly, the most likely use you will find for a custom WebPermission is in partial trust ASP.NET applications that call into web services. Without defining one or more WebPermissions, your web service calls will fail with less than enlightening security errors. Download at Boykma.Com 177 Chapter 4: A Matter of Trust Because your web application may need to connect to multiple web service endpoints, potentially located under different DNS namespaces, you need to define a element in your custom policy file with multiple nested entries. As an example, the following code gives you the correct XML representation for a set of two different endpoints: C# WebPermission wp = new WebPermission(); Regex r = new Regex(@”http://www\.microsoft\.com/.*”); wp.AddPermission(NetworkAccess.Connect,r); r = new Regex(@”http://www\.google\.com/.*”); wp.AddPermission(NetworkAccess.Connect, r); SecurityElement se = wp.ToXml(); Response.Write(Server.HtmlEncode(se.ToString())); VB.NET Dim wp As New WebPermission() Dim r As New Regex(“http://www\.microsoft\.com/.*”) wp.AddPermission(NetworkAccess.Connect, r) r = New Regex(“http://www\.google\.com/.*”) wp.AddPermission(NetworkAccess.Connect, r) Dim se As SecurityElement = wp.ToXml() Response.Write(Server.HtmlEncode(se.ToString())) The resulting XML, adjusted again for the class attribute, looks like this: The $OriginHost$ replacement token is no longer being used. Realistically, after you understand how to define a WebPermission in your policy file, the originUrl attribute isn’t really needed anymore. Instead, you can just build up multiple elements as needed inside of your policy file. With the previous changes, you can now write code that connects to any page located underneath www.microsoft.com or www.google.com. C# HttpWebRequest wr = (HttpWebRequest)WebRequest.Create(“http://www.microsoft.com/games/default.aspx”); HttpWebResponse resp = (HttpWebResponse)wr.GetResponse(); … 178 Download at Boykma.Com Chapter 4: A Matter of Trust resp.Close(); wr = (HttpWebRequest)WebRequest.Create(“http://www.google.com/microsoft”); resp = (HttpWebResponse)wr.GetResponse(); VB.NET Dim wr As HttpWebRequest = CType( _ WebRequest.Create(“http://www.microsoft.com/games/default.aspx”), _ HttpWebRequest) Dim resp As HttpWebResponse = CType(wr.GetResponse(), HttpWebResponse) … resp.Close() wr = CType( _ WebRequest.Create(“http://www.google.com/microsoft”), _ HttpWebRequest) resp = CType(wr.GetResponse(), HttpWebResponse) Although I won’t cover it here, the companion classes to HttpWebRequest/HttpWebResponse are the various System.Net.Socket* classes. As with the Http classes, the socket classes have their own permission: SocketPermission. Just like WebPermission, SocketPermission allows the definition of network endpoints for both socket connect and socket receive operations. LINQ in Medium/Partial Trust ASP.NET Applications Language Integrated Query (LINQ), introduced in the .NET Framework 3.5, is a standard way of accessing data, whether the data is stored in databases, XML files, objects, or other data sources. The purpose behind LINQ is to provide a standard set of query operators that the developer can make use of to query against different data sources by utilizing the same queries with some or minor changes between a data source and another. By default, LINQ features, prior to the .NET Framework 3.5 final release mainly in .NET Framework 3.5 Beta 2, cannot run in an ASP.NET application that is configured with medium or partial trust. As you have learned above, the set of permissions granted to an ASP.NET web application running in the medium or partial trust is determined by a Code Access Security (CAS) policy file located on the web server. When the .NET Framework 3.5 Beta 2 is installed on the machine, the existing or new ASP.NET web applications or websites continue to use the same CAS policy files that were defined with the .NET Framework 2.0. However, this has been resolved with the final release of the .NET Framework 3.5 in such a way that when you install the .NET Framework 3.5 on the machine, the CAS policy files get updated and modified to reflect the permission changes required to make LINQ function properly in ASP.NET applications running under the medium or partial trust levels. The following paragraphs describe a step-by-step process to show you the permissions required by LINQ to function properly in web applications running in the medium or partial trust, and how to configure them manually. Remember, the following configurations are already done for you when you install the .NET Framework 3.5 final release on your machine. LINQ features require the application running inside it to be granted the RestrictedMemberAccess permission, which is not granted by default for ASP.NET 2.0 running in medium or partial trust. The RestrictedMemberAccess permission indicates whether the restricted invocation of non-public types and members is allowed or not for partially trusted code. The restricted invocation means that for a partially trusted code to access non-public types and members, the set of permissions granted to it must contain all the permissions granted to the assembly that has the non-public types and members. Download at Boykma.Com 179 Chapter 4: A Matter of Trust Before getting into how to enable an ASP.NET application running in the medium or partial trust level to function properly with LINQ, let’s look at a sample code that makes use of a LINQ to SQL query: C# PubsDataContext context = new PubsDataContext(); var query = from emp in context.employees select emp; foreach (employee empl in query) { Response.Write(“Employee Name: “ + empl.fname + “
”); } VB.NET Dim context As New PubsDataContext() Dim query = From emp In context.employees _ Select emp For Each empl As employee In query Response.Write(“Employee Name: “ & empl.fname & “
”) Next empl The preceding code assumes that there is a PubsDataContext created in the application that points to the famous Pubs database on Microsoft SQL Server 2000 or 2005. Without going into much detail on the DataContext class, consider it as the gateway to access the data tables that were loaded from the database and converted into .NET objects. The same above shows a simple query that retrieves all the employees’ records from the Employees data table. Once the data is retrieved, a foreach-loop goes through every item returned and displays onscreen the first name of every employee in the result set of the query. When you run the preceding code in an ASP.NET application configured with a Medium trust level in such a way the machine running the application is still using the unmodified .NET Framework 2.0 CAS policy files, you will receive the following exception: [SecurityException: Request for the permission of type ‘System.Security. Permissions.ReflectionPermission, mscorlib, Version=2.0.0.0, Culture=neutral, Publi cKeyToken=b77a5c561934e089’ failed.] As you can see, a request for the permission of type ReflectionPermission is done to allow the code to browse the members on the employee class above. Hence, the need for granting access for the ReflectionPermission for the LINQ features to work properly in an ASP.NET web application running under the medium or partial trust level. To allow ASP.NET web applications running under the .NET Framework 3.5 to use the new LINQ features, you need to modify the CAS policy file that corresponds to the trust level that is configured for the applications, which is a task that has been already done for you when the .NET Framework 3.5 final release was installed on your machine. Assuming that you have installed .NET Framework 3.5 Beta 2 and your application is configured with the medium trust level, the changes should target the web_mediumtrust.config configuration file. To start, open the aforementioned configuration file, which located in the following directory on your machine: %windir%\Microsoft.NET\Framework\v2.0.50727\CONFIG 180 Download at Boykma.Com Chapter 4: A Matter of Trust Make sure to back up the CAS policy file you want to modify. This is recommended so that no harm is generated on your existing ASP.NET web applications in the event something wrong went on while modifying the policy file. Locate the configuration section inside the web_mediumtrust.config file and check if an entry is found for the ReflectionPermission class. If not, make sure to add the following entry: The ReflectionPersmission class controls access to non-public types and members through the System.Reflection classes with the help of the appropriate ReflectionPermissionFlag enumeration. Once the preceding entry is added, you also need to add the following entry to the , as follows:
For the LINQ features to function properly, the ReflectionPermission should be granted on the CAS policy file with the ReflectionPermissionFlag set to RestrictedMemberAccess. That is everything you need to do to make the LINQ features function properly in an application configured to run with the medium trust level. If you are using another trust level that already contains an entry for the ReflectionPersmission, make sure that ReflectionPermissionFlag contains a value of RestrictedMemberAccess, as follows: Save the file and make sure to restart the IIS 7.0 web server. Now running the sample code above should work smoothly without any exceptions. Remember that the preceding changes and demonstration is required only on a machine that has .NET Framework 3.5 Beta 2 installed and not the final release of the framework; as you know by now, .NET Framework 3.5 final release modifies the CAS policy files on your behalf. However, the demonstration was to show you in depth what LINQ features require to function properly in applications running under .NET Framework 3.5 and configured with medium or partial trust levels. The Default Security Permissions Defined by ASP.NET ASP.NET ships with default trust policy files for High, Medium, Low, and Minimal trust. You have already read about several different permissions that are configured in these files. This section covers all the permissions that appear in the files in the ASP.NET named permission set, along with information on the different rights that are granted depending on the trust level. Download at Boykma.Com 181 Chapter 4: A Matter of Trust AspNetHostingPermission To support the trust level model, ASP.NET created a new permission class: System.Web.AspNet​ HostingPermission. The permission class is used as the runtime representation of the application’s configured trust level. Although you could programmatically determine the trust level of an application by looking at the level attribute of the element, that programming approach isn’t consistent with how you would normally use CAS permissions. Because AspNetHostingPermission inherits CodeAccessPermission, code can instead demand an AspNetHostingPermission just like any other permissions class. The Framework will perform its stack walk, ensuring that all code in the current call stack has the demanded trust level. ASP.NET uses this capability extensively within its runtime to protect access to pieces of functionality that are not intended for use at lower trust levels. The permission class has a public property Level that indicates the trust level represented by the permission instance. In the various trust policy files, there is always a definition of AspNetHostingPermission. The usual convention is to set the Level attribute in the element to the effective trust level represented by the policy file. There is nothing to prevent you from setting the Level attribute to a value that is inconsistent with the overall intent of the trust policy file. For example, you could declare an AspNetHostingPermission with a Level of High inside of the minimal trust policy file. However, you should normally not do this, because the value of the Level property is used by ASP.NET to protect access to certain pieces of functionality. Artificially increasing the trust level can result in ASP.NET successfully checking for a specific trust level and then failing with SecurityException when the runtime attempts a privileged operation that isn’t allowed based on the other permissions defined in the trust policy file. The problem also exists with the reverse condition; you could define a lower trust level than what the permissions in the trust policy file would normally imply. For example, you could copy the policy file for High trust, and then change the AspNetHostingPermission definition’s Level attribute to Medium. Even though ASP.NET internally won’t run into unexpected exceptions, you now have the problem that ASP.NET “thinks” it is running at Medium trust, but the permissions granted to the application are actually more appropriate for a High trust application. All this brings us to a very important point about the AspNetHostingPermission. The intent of the Level property is to be a broad indicator of the level of trust that you are willing to associate with the application. Although the definitions in the rest of the policy file are a concrete representation of the trust level, the Level property is used as a surrogate for making other trust-related decisions in code. Whenever possible you should set the Level attribute appropriately based on the level of trust you are willing to grant to the application. Internally ASP.NET needs to make a number of security decisions based on an application’s trust level. Rather than creating concrete permissions for each and every security decision (this would result in dozens of new permission classes at a bare minimum), ASP.NET instead looks at the AspNetHostingPermission for an application and makes security judgments based on it. This is the main reason why you should ensure that the “Level” attribute is set appropriately for your application. 182 Download at Boykma.Com Chapter 4: A Matter of Trust Trust Level Intent So, what specifically are the implications behind each trust level? Full trust is easy to understand because it dispenses with the need for a trust policy file and a definition of AspNetHostingPermission. The following table lists the conceptual intent behind the other trust levels. Trust Level Full High Medium Low Intent The ASP.NET application can call anything it wants. The ASP.NET application should be allowed to call most classes within the .NET Framework without any restrictions. Although the High trust policy file does not contain an exhaustive list of all possible Framework permissions (the file would be huge if you attempted this), High trust implies that aside from calling into unmanaged code (this is disallowed), it is acceptable to use most of the remainder of the Framework’s functionality. Although sandboxing privileged operations in GAC’d classes is preferred, adding new permissions directly to the High trust policy file instead would not be considered “breaking the contract” of High trust. The ASP.NET application is intended to be constrained in terms of the classes and Framework functionality it is allowed to use. A Medium trust application isn’t expected to be able to directly call dangerous or privileged pieces of code. However, a Medium trust application is expected to be able to read and write information; it is just that the reading and writing may be constrained, or require special permissions before it is allowed. If problems arise because of a lack of permissions, you try to avoid adding the requisite permission classes to the Medium trust policy file. Instead, if privileged operations require special permissions, the code should be placed in a separate assembly and installed in the GAC. Furthermore, if at all possible, this type of assembly should demand some kind of permission that you would expect the Medium trust application to possess. For example you could demand the AspNetHostingPermission at the Medium level to ensure that even less trusted ASP.NET applications cannot call into your GAC’d assembly. The ASP.NET application is running in an environment where user code should not be trusted with any kind of potentially dangerous operations. Low trust applications are frequently considered to be read-only applications; this would cover things like a reporting application. Because this is such a “low” level of trust, you should question any application running in this trust level that is allowed to reach out and modify data. For example, in the physical world someone that you had a low level of trust for is probably not an individual you would trust to make changes to your bank account balance. As with Medium trust, you should use GAC’d assemblies to solve permission problems, although you should look at the operations allowed in your assemblies to see if they are really appropriate for a Low trust application. Note that Low trust is also appropriate for web applications like SharePoint that provide their own hosted environment and thus their own security model on top of ASP.NET. Applications like SharePoint lock down the rights of pages that are just dropped on the web server’s file system. Developers instead make use of privileged functionality through the SharePoint APIs or by following SharePoint’s security model. Continued Download at Boykma.Com 183 Chapter 4: A Matter of Trust Trust Level Minimal Intent A Minimal trust application means that you don’t trust the code in the application to do much of anything. If permission problems arise, you should not work around the issue with GAC’d assemblies. Instead, you should question why a minimally trusted application needs to carry out a protected operation. Realistically, this means that a Minimal trust application is almost akin to serving out static HTML files, with the additional capability to use the ASP.NET page model for richer page development. ASP.NET Functionality Restricted by Trust Level ASP.NET makes a number of decisions internally based on the trust level defined by the AspNetHosting​ Permission. Because High and Full trust applications imply the ability to use most Framework functionality, the allowed ASP.NET functionality at these levels isn’t something you need to worry about. However, the Medium trust level is the lowest level at which the following pieces of ASP.NET functionality are allowed. Below Medium trust, the following features and APIs are not allowed: ❑❑ Accessing asynchronous pages (the Async page attribute) ❑❑ Accessing transacted pages (the Transaction page attribute) ❑❑ Using the Culture page attribute ❑❑ Setting debug=true for a page or the entire application ❑❑ Sending mail with System.Web.Mail.SmtpMail ❑❑ Calling Request.LogonUserIdentity ❑❑ Calling Response.AppendToLog ❑❑ Explicitly calling HttpRuntime.ProcessRequest ❑❑ Retrieving the MachineName property from HttpServerUtility ❑❑ Setting the ScriptTimeout property on HttpServerUtility ❑❑ Using the System.Web.Compilation.BuildManager class ❑❑ Displaying a source error and source file for a failing pages At Low trust, there are a still a few pieces of ASP.NET functionality available that are not allowed when running at Minimal trust: ❑❑ Retrieving Request.Params. ❑❑ Retrieving Request.ServerVariables. ❑❑ Retrieving HttpRuntime.IsOnUNCShare. ❑❑ Calling into the provider-based features: Membership, Role Manager, Profile, Web Parts Person- alization, and Site Navigation. Note, though, that most of the providers for these features will not work in Low trust because their underlying permissions are not in the Low trust policy file. 184 Download at Boykma.Com Chapter 4: A Matter of Trust Implications of AspNetHostingPermission Outside of ASP.NET As you may have inferred from the name of the permission, it is primarily intended for use with ASP.NETspecific code. Most of the time, this means Framework code that has the AspNetHostingPermission attribute or that internally demands this permission to be called from inside of ASP.NET. In fully trusted code-execution environments outside of ASP.NET you may not realize this is happening. For example, the following code runs without a problem in a console application. C# Console.WriteLine(HttpUtility.HtmlEncode(“
”)); VB.NET Console.WriteLine(HttpUtility.HtmlEncode(“
”)) Notice that this code is using the System.Web.HttpUtility class. Running the console application from the local hard drive works, even though the HttpUtility class has the following declarative LinkDemand: C# [AspNetHostingPermission(SecurityAction.LinkDemand, Level=AspNetHostingPermissionLevel.Minimal] VB.NET This works by default because applications running from the local hard drive are considered by the .NET Framework to be running in the My Computer security zone. Any code running from this zone is fully trusted. As a result, when it evaluates the LinkDemand, the Framework the application is running in full trust, and thus ignores any permission checks. However, if you move the compiled executable to a universal naming convention (UNC) share and then run it, you end up with a SecurityException and the following stack dump information: System.Security.SecurityException: Request for the permission of type ‘System.Web. AspNetHostingPermission, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b 77a5c561934e089’ failed. …. The assembly or AppDomain that failed was: UsingAspNetCodeOutsideofAspNet, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null The Zone of the assembly that failed was: Internet The Url of the assembly that failed was: file://remoteserver/c$/ UsingAspNetCodeOutsideofAspNet.exe Now the Framework considers the application to be running in partial trust. Because the executable was moved to a UNC share, the Framework applied the security restrictions from the Internet zone. When LinkDemand occurred for AspNetHostingPermission, the Framework looked for that permission in the named permission set that the Framework associates with the Internet zone. Of course, it couldn’t find it because the AspNetHostingPermission is typically found only inside of the ASP.NET trust policy files. Download at Boykma.Com 185 Chapter 4: A Matter of Trust I won’t cover how to fix this security problem in this chapter, because most of the ASP.NET classes are not intended for use outside of a web application anyway. However, in Chapter 15, “SqlRoleProvider,” I walk through an example of using a provider-based feature from inside of a partial trust non-ASP.NET application. Both Membership and Role Manager are examples of ASP.NET classes that were explicitly tweaked to make them useable outside of a web application. However, the classes for these features make extensive use of AspNetHostingPermission, so it is necessary to understand how to grant the AspNetHostingPermission to partial trust non-ASP.NET applications that use these two features. Using AspNetHostingPermission in Your Code Because AspNetHostingPermission models the conceptual trust that you grant to an application, you can make use of this permission as a surrogate for creating a permission class from scratch. In fact, one of the reasons ASP.NET uses AspNetHostingPermission to protect certain features is to reduce the class explosion that would occur if every protected feature had its own permission class. So, rather than creating TransactedPagePermission, AsyncPagePermission, SetCultureAttributePermission, and so on, ASP.NET groups functionality according to the trust level that is appropriate for the feature. You can follow a similar approach with standalone assemblies that you author. This applies to custom control assemblies as well as to assemblies that contain middle-tier code or other logic. For example, you can create a standalone assembly that uses the permission with the following code: C# public class SampleBusinessObject { public SampleBusinessObject() { } public string DoSomeWork() { AspNetHostingPermission perm = new AspNetHostingPermission(AspNetHostingPermissionLevel.Medium); perm.Demand(); //At this point it is safe to perform privileged work return “Successfully passed the permission check.”; } } VB.NET Public Class SampleBusinessObject Public Sub New() End Sub Public Function DoSomeWork() As String Dim perm As New AspNetHostingPermission( _ AspNetHostingPermissionLevel.Medium) perm.Demand() ‘At this point it is safe to perform privileged work 186 Download at Boykma.Com Chapter 4: A Matter of Trust Return “Successfully passed the permission check.” End Function End Class Drop the compiled assembly into the /bin folder of an ASP.NET application. Because the assembly demands Medium trust, the following simple page code in an ASP.NET application works at Medium trust or above. C# SampleBusinessObject obj = new SampleBusinessObject(); Response.Write(obj.DoSomeWork()); VB.NET Dim obj As New SampleBusinessObject() Response.Write(obj.DoSomeWork()) However, if you configure the ASP.NET application to run at Low or Minimal trust, the previous code will fail with a SecurityException stating that the request for the AspNetHostingPermission failed. Unfortunately, though, the exception information will not be specific enough to indicate additional any extra information; in this case, it would be helpful to know the level that was requested but failed. In cases like this where you probably control or have access to the code in the standalone assemblies, you can determine which security permissions are required by using the permcalc tool located in the .NET Framework’s SDK directory. (This directory is available underneath the Visual Studio install directory if you chose to install the SDK as part the Visual Studio setup process.) I ran permcalc against the sample assembly with the following command line: “C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin\permcalc” SampleBusinessTier.dll The tool outputs an XML file containing all declarative and code-based permission demands. Although declarative permission requirements are the easiest to infer (remember there is also an AspNetHosting​ Permission attribute that you can use to adorn a class or a method), the tool does a pretty good job of inspecting the actual code and pulling out the code-based permission demands. In the case of the sample assembly, it returned the following snippet of permission information: - - The element in the permcalc output shows that the tool determined that the DoSomeWork method is demanding AspNetHostingPermission with the Level at Medium. Download at Boykma.Com 187 Chapter 4: A Matter of Trust DnsPermission As the name implies, the System.Net.DnsPermission class defines the ability of your code to perform forward and reverse address lookups with the System.Net.Dns class. The permission is a binary permission in that it either grants code the right call into the Dns class or it denies the ability to use the Dns class. An interesting side note is that if you do not add DnsPermission to a trust policy file, but you have added WebPermission, you can still make use of the HttpWebRequest and related classes. Internally, System.Net assumes that if you have the necessary WebPermission, it can perform any required DNS lookups internally on your behalf. The rights for DnsPermission at the various trust levels are shown in the following table: Trust Level Full High Medium Low Minimal Granted Permission Unrestricted Unrestricted Unrestricted No rights to use the Dns class No rights to use the Dns class EnvironmentPermission The System.Security.Permissions.EnvironmentPermission class defines the ability of user code to access environment variables via the System.Environment class. If you drop to a command line and run the SET command, all sorts of interesting information is available from the environment variables. Because this could be used as a backdoor for gathering information about the web server, the ASP.NET trust policy files restrict access to only a few environment variables in the lower trust levels. The EnvironmentPermission supports defining access levels on a more granular basis, even down to the level of protecting individual environment variables. As a result, you can control the ability to read and write individual environment variables. Each security attribute (All, Read, and Write) in the declarative representation of an EnironmentPermission can contain a semicolon delimited list of environment variables. The rights for EnvironmentPermission at the various trust levels are shown in the following table: Trust Level Full High Medium Low Minimal Granted Permission Unrestricted Unrestricted Can only read the following environment variables: TEMP, TMP, USERNAME, OS, COMPUTERNAME. No ability to set environment variables. No rights to read or write any environment variables No rights to read or write any environment variables 188 Download at Boykma.Com Chapter 4: A Matter of Trust FileIOPermission I have already covered most of the functionality for the System.Security.Permissions.File​ IOPermission class in other sections. This permission also supports defining different permissions for different directory and file paths. The thing that is a little odd about this permission class is that it takes a somewhat nonoptimal approach to declaring multiple permissions. Unlike WebPermission or SocketPermission, FileIOPermission does not output nested elements within a element. Instead, it has a fixed set of attributes, but each path-related attribute can contain a semicolondelimited list of multiple paths. For example, the declarative syntax of a FileIOPermission with different permissions for two different directory paths is shown here: This permission defines only allowable file I/O operations at the Framework level. This means the permission class is only able to define the ability of user code to perform logical operations (read, write, and so on based on a set of defined file paths. However, the FileIOPermission does not protect access to files and directories based on NT file system (NTFS) file ACLs. As a result, it is completely possible that from a CAS perspective the Framework will allow your code to issue a file I/O operation, but from an NTFS perspective, your code may not have the necessary security permissions. When performing any type of file I/O, you also need to ensure that the identity of the operating system thread has been granted the necessary rights on the file system. The following table lists the default permissions for the different trust levels. Trust Level Full High Medium Low Minimal Granted Permission Unrestricted. Unrestricted: Remember, this means the ability to read and write files anywhere in the file system. Read, write, append, and path discovery are all allowed for directories and paths located within the directory structure of the web application. Operations outside of the application’s directory structure are not allowed. Only read and path discovery are all allowed for directories and paths located within the directory structure of the web application. Write operations are not allowed within the application’s directory structure. Also, operations outside of the application’s directory structure are not allowed. No file I/O rights. IsolatedStorageFilePermission The System.Security.Permissions.IsolatedStorageFilePermission class controls the allowable file operations when using the System.IO.IsolatedStorage.IsolatedStorageFile class. I honestly Download at Boykma.Com 189 Chapter 4: A Matter of Trust have never encountered any customers using isolated file storage in an ASP.NET application. Although you could technically use isolated storage as a way to store information locally on the web server for each website user, there are probably not any web applications that work this way: A database would be better choice, especially in web farm environments. However, because IsolatedStoragePermission is also defined by the Framework in the machine CAS policy, the permission is included in the ASP.NET trust policy files to ensure that ASP.NET has the final say on what is allowed when using isolated storage. The following table lists the default permissions for the different trust levels. Trust Level Full High Medium Low Minimal Granted Permission Unrestricted. Unrestricted. Isolated storage is allowed, but the only storage mode that can be used isolates data by user identity. The disk quota for each user is effectively set to infinite. Isolated storage is allowed, but the only storage mode that can be used isolates data by user identity. The disk quota for each user is set to 1MB. Not allowed. PrintingPermission Before you double over laughing at why this permission exists in an ASP.NET trust policy file, I’ll state that the reason is the same as mentioned earlier for the IsolatedStorageFilePermission. The default machine CAS policy grants System.Drawing.Printing.PrintingPermission to code running in the various predefined security zones. So, ASP.NET also defines the PrintingPermission in its trust files to ensure that it has a final say in the level of access granted to user code that works with printers. The following table lists the default permissions for the different trust levels. Trust Level Full High Medium Low Minimal Granted Permission Unrestricted. User code can issue commands to print to the default printer attached to the web server. User code can issue commands to print to the default printer attached to the web server. Not allowed. Not allowed. ReflectionPermission The System.Security.Permissions.ReflectionPermission class defines the types of reflection operations you can perform with classes in the System.Reflection namespaces. This is a very important permission for ensuring the safety of partial trust applications because reflecting against code 190 Download at Boykma.Com Chapter 4: A Matter of Trust introduces the potential for calling private/internal methods, and inspecting private/internal variables. As a result, in the default ASP.NET policy files only High trust code has rights to use some of the reflection APIs. In practice, you should not grant reflection permission to partially trusted user code due to the potential for malicious code to deconstruct the code that is running on your server. The following table lists the default permissions for the different trust levels. Trust Level Full High Medium Low Minimal Granted Permission Unrestricted. User code can use only classes in the System.Reflection.Emit namespace. These classes can be used to generate code programmatically as well as a compiled representation of the generated code. This functionality can be useful for an application that dynamically generates assemblies to disk and then references these classes from page code. Not allowed. Not allowed. Not allowed. RegistryPermission The System.Security.Permissions.RegistryPermission defines permissions for creating, reading, and writing Registry keys and values. Much as with FileIOPermission, you can use this permission class to define a set of permission rules that vary depending on the Registry path. The various security attributes on the element contain a semicolon delimited list of Registry keys to protect. This permission is enforced whenever you use the Microsoft.Win32.RegistryKey class to manipulate the registry. Because there usually isn’t a need to directly read and write Registry data in web applications, ASP.NET by default only defines a RegistryPermission for High trust. If you need access to Registry information at lower trust levels, you should put Registry access code into a separate GAC’d assembly that has the necessary permissions. Normally, though, the restrictions on Registry access are not too onerous because in web applications you use configuration files as opposed to Registry keys for storing application configuration data. The following table lists the default permissions for the different trust levels. Trust Level Full High Medium Low Minimal Granted Permission Unrestricted Unrestricted Not allowed Not allowed Not allowed Download at Boykma.Com 191 Chapter 4: A Matter of Trust SecurityPermission TheSystem.Security.Permissions.SecurityPermission class is a proverbial jack-of-all-trades permissions class. Instead of defining a narrow set of permissions used by a specific set of classes in the framework, a SecurityPermission class can define around fifteen permissions that apply to different privileged operations in the framework. For example, these permissions define the ability to call unmanaged code and the ability for code to execute. The list of possible permissions that can be granted with a SecurityPermission can be found in the SecurityPermissionFlag enumeration. In partial trust applications, ASP.NET allows a subset of the available permissions by defining progressively more restrictive security permissions for the lower trust levels. The specific permissions that ASP.NET may grant are listed here: ❑❑ Assertion: This permission allows code to assert that it has the right to call into other code that may demand certain permissions. The advanced topics sections of this chapter cover how to write GAC’d assemblies that use this permission. In partially trusted applications, assertion is usually not granted because code doesn’t have sufficient rights to assert other arbitrary permission defined in the Framework. ❑❑ ControlPrincipal: Allows code to change the IPrincipal reference available from Thread​ .CurrentPrincipal. ASP.NET also demands this right if you attempt to set the User property on an HttpContext. Keep this permission in mind if you write custom authentication or custom authorization modules. If your modules need to set the thread principal when running in Low trust or below, you need to deploy your modules in the GAC and assert a Security​ Permission with the ControlPrincipal right. ❑❑ ControlThread: Grants code the right to perform privileged operations on an instance of System​ .Threading.Thread. For example, with this permission code is allowed to call Thread.Abort, Thread.Suspend, and Thread.Resume. ❑❑ Execution: Allows .NET Framework code to run. If ASP.NET did not define this permission in the various trust policy files, none of your code would ever be allowed to run. Removing this permission from any of the ASP.NET trust policy files effectively disables the ability to run .aspx pages. ❑❑ RemotingConfiguration: Allows an application to configure and start up a remoting infrastructure. Many ASP.NET applications don’t need to expose or call into remotable objects. However, if you want to run a partial trust ASP.NET application that consumes objects using .NET Remoting, make sure this permission is defined in the trust policy file. Note that RemotingConfiguration isn’t needed if your application calls Web Services. The following table lists the security permissions granted at the different trust levels. Trust Level Full High Medium Low Minimal Granted Permission Unrestricted Assertion, Execution, ControlThread, ControlPrincipal, RemotingConfiguration Assertion, Execution, ControlThread, ControlPrincipal, RemotingConfiguration Execution Execution 192 Download at Boykma.Com Chapter 4: A Matter of Trust As you can see from this list, at Low and Minimal trust user code has only the ability execute. Because ASP.NET restricts the SecurityPermission at Low and Minimal trust, you need to deploy all sensitive business or security logic in GAC’d assemblies. Due to the sensitive nature of the Assertion and ControlPrincipal rights, you should look into removing these if you create a custom trust level. The Assertion right is really intended for trusted code that can successfully assert some kind of underlying permission. However, partially trusted code by its very nature lacks many permissions, and thus it is unlikely that user code in a code-behind page could successfully assert a permission (if the code already had the necessary permission, it wouldn’t need to assert anything in the first place). The ControlPrincipal right is a security-sensitive right appropriate only for code that manipulates identity information for a request. Although it is a little bit more difficult to write a standalone HTTP authentication/authorization module and deploy it in the GAC, it is much more secure to do so and then remove the ControlPrincipal right in a trust policy file. Doing so ensures that some random piece of application code can’t arbitrarily change the security information for a request, something especially trivial to accomplish when using forms authentication. SmtpPermission In ASP.NET 1.0 and 1.1, the closest thing to a managed mail class was found in System.Web.Mail​ .SmtpMail. Internally, SmtpMail is just a wrapper around CDONTS, which itself is unmanaged code. Because it would be excessive to grant unmanaged code permission to a partially trusted ASP.NET application, ASP.NET instead protects access to this mail class by using the AspNetHostingPermission as surrogate permission. At Medium trust or above, you can use SmtpMail, whereas at lower trust levels you cannot send mail. Starting with the v2.0 of the Framework, though, the System.Web.Mail.SmtpMail class has been deprecated and is replaced by the classes in the System.Net.Mail namespace. These classes protect access to mail operations using the System.Net.Mail.SmtpPermission class. To maintain parity with the mail behavior of earlier ASP.NET release, the trust policy files are defined to allow all mail operations at Medium trust and above as shown in the following table. Trust Level Full High Medium Low Minimal Granted Permission Unrestricted Unrestricted Unrestricted Not allowed Not allowed SocketPermission System.Net.SocketPermission is the companion permission class to the System.Net.WebPermission class discussed earlier. It supports defining connect and receive access in a granular fashion segmented by different network endpoints. Because of the potential for mischief when using the socket classes, ASP.NET grants access to only High trust applications. If you have web applications that need to make outbound socket connections (receiving socket connections is unlikely in a web application), you can Download at Boykma.Com 193 Chapter 4: A Matter of Trust use the same approach described earlier for the WebPermission class to determine the exact XML syntax necessary to restrict socket connections to specific endpoints. The following table lists the security permissions granted at the different trust levels. Trust Level Full High Medium Low Minimal Granted Permission Unrestricted Unrestricted Not allowed Not allowed Not allowed SqlClientPermission The System.Data.SqlClient.SqlClientPermission class is used to allow or disallow use of the classes in the System.Data.SqlClient namespace. There is no support for granular permissions along the lines of the SocketPermission or WebPermission classes. Because Medium trust is the recommended default trust level for shared hosters, the permission is available at Medium trust and above. The following table lists the security permissions granted at the different trust levels. Trust Level Full High Medium Low Minimal Granted Permission Unrestricted Unrestricted Unrestricted Not allowed Not allowed WebPermission System.Net.WebPermission is used to define a granular set of connection rules for making HTTP requests to various network endpoints. Because it is a potentially complex permission with multiple nested permission elements, you can use the techniques described in the section “Using the WebPermission” to determine the correct XML. The following table lists the security permissions granted at the different trust levels. Trust Level Full High Granted Permission Unrestricted. Unrestricted. 194 Download at Boykma.Com Chapter 4: A Matter of Trust Trust Level Medium Low Minimal Granted Permission Only connect access is granted to a single network endpoint. This endpoint is defined by the originUrl attribute in the configuration element. Not allowed. Not allowed. Advanced Topics on Partial Trust There are a few advanced issues on partial trusts that you may encounter while developing your application: ❑❑ Exception behavior when dealing with Link demands ❑❑ Requirements for using the “allow partially trusted callers attribute” (APTCA) when writing trusted types for use by ASP.NET ❑❑ Sandboxing access to security sensitive code with GAC’d assemblies ❑❑ The processRequestInApplicationTrust attribute in the element LinkDemand Exception Behavior All the sample code used so far to highlight exception behavior has involved full permission demands made by different classes in the Framework. However, this type of permission demand can be expensive because the Framework has to crawl up the current call stack each and every time a full permission demand occurs. Even if the exact same code is executing on subsequent page requests, the Framework still has to perform a fair amount of work to reevaluate the results of a demand. To mitigate the performance hit of full demands, the Framework also includes the concept of a link demand, also referred to as a LinkDemand. The idea behind a LinkDemand is that the Framework needs to make a permission check only the first time code from one assembly attempts to call a piece of protected code in another assembly. After that check is made, the Framework does not perform any additional security evaluations on subsequent calls. The issue you may run into when developing partial trust applications is that LinkDemands are evaluated before your code even starts running. The reason for this is that a LinkDemand occurs when the Framework is attempting to link the code that you wrote with the compiled code that exists in another assembly. Establishing this link occurs before the first line of code in your method executes. As a result, even though you may have try/catch blocks set up to explicitly catch SecurityExceptions, you still end up with an unhandled exception. To highlight this behavior, let’s use one of the sample pieces of code from the beginning of the chapter to make a call into the ADO PIA. C# try { //An unhandled exception due to LinkDemands will occur before this code runs RecordsetClass rc = new RecordsetClass(); Download at Boykma.Com 195 Chapter 4: A Matter of Trust int fieldCount = rc.Fields.Count; Response.Write(“Successfully created an ADO recordset using the ADO PIA.”); } catch (Exception ex) { Response.Write(ex.Message + “
” + Server.HtmlEncode(ex.StackTrace)); } VB.NET Try ‘The next two lines of code result ‘ ‘in an unhandled exception from the LinkDemand() Dim rc As New RecordsetClass() Dim fieldCount As Integer = rc.Fields.Count Response.Write( _ “Successfully created an ADO “ & _ “recordset using the ADO PIA.”) Catch ex As Exception Response.Write(ex.Message & “
” & _ Server.HtmlEncode(ex.StackTrace)) End Try Even though this code is catching almost every exception, when you attempt to run this code in a partial trust ASP.NET application (I used Medium trust for the test), the page fails with an unhandled exception. Some of the abbreviated exception information is shown here: [SecurityException: That assembly does not allow partially trusted callers.] System.Security.CodeAccessSecurityEngine.ThrowSecurityException(Assembly asm, PermissionSet granted, PermissionSet refused, RuntimeMethodHandle rmh, SecurityAction action, Object demand, IPermission permThatFailed) at LinkDemand. CreateRecordset() at LinkDemand.Button1_Click(Object sender, EventArgs e) in c:\ inetpub\wwwroot\379301_code\379301 ch04_code\cs\WorkingWithTrustLevels\LinkDemand. aspx.cs:line 36 The call stack shows the code appears to have transitioned from the button click handler immediately into the internals of the .NET Framework security system. The reason is that the ADO primary interop assembly (PIA) is installed in the GAC, and thus the Framework requires that any calling code itself be fully trusted. The security check immediately failed when it detected that the calling code was partially trusted. In fact, one of the most common symptoms of a failed LinkDemand is the exception text stating that some assembly doesn’t allow partially trusted callers. The way around the unhandled exception problem is to place code that may encounter LinkDemand failures inside of a separate method or function. Then have your main code path call the helper method, wrapping the call in an exception handler. For example, you can change the sample code to use a private method for calling ADO: 196 Download at Boykma.Com Chapter 4: A Matter of Trust C# private void CreateRecordset() { //This code will never run due to a LinkDemand failure RecordsetClass rc = new RecordsetClass(); int fieldCount = rc.Fields.Count; } protected void Button1_Click(object sender, EventArgs e) { try { //The LinkDemand failure from the private method will bubble up as a //catch-able exception this.CreateRecordset(); Response.Write(“Successfully created an ADO recordset using the ADO PIA.”); } catch (Exception ex) { Response.Write(ex.Message + “
” + Server.HtmlEncode(ex.StackTrace)); } } VB.NET Private Sub CreateRecordset() Dim rc As New RecordsetClass() Dim fieldCount As Integer = rc.Fields.Count End Sub Protected Sub Button1_Click( _ ByVal sender As Object, _ ByVal e As System.EventArgs) Handles Button1.Click Try ‘ The LinkDemand failure from the private method will bubble up as a ‘ catch-able exception Me.CreateRecordset() Response.Write( _ “Successfully created an ADO “ & _ “recordset using the ADO PIA.”) Catch ex As Exception Response.Write(ex.Message & “
” & _ Server.HtmlEncode(ex.StackTrace)) End Try End Sub Now the LinkDemand failure occurs when the Framework attempts to link the code in CreateRecordset to the code inside of the ADO PIA. The resulting SecurityException is successfully caught inside of the button click handler, and you can react appropriately to the error. Download at Boykma.Com 197 Chapter 4: A Matter of Trust Although this example demonstrates the problem with a LinkDemand requiring a full trust caller, any LinkDemand-induced failure will exhibit this behavior. As a developer, you should be aware of this and code defensively when you know you are using classes that implement LinkDemands. LinkDemand Handling When Using Reflection Because LinkDemands are intended to protect an assembly when another assembly links to it, there is a potential problem when using reflection to call into a protected assembly. With reflection, the immediate caller into a protected assembly is the .NET Framework code for the System.Reflection namespace. Because Framework code all lives in the GAC, any LinkDemand would appear to immediately pass the security checks. However, if this were really the case, any partial trust application with the appropriate ReflectionPermission could subvert the intent of a LinkDemand. To prevent this kind of “end run” around security, the Framework first checks the security of the true caller rather than the code running System.Reflection. Additionally, the Framework converts the LinkDemand into a full demand. If the previous example used a GAC’d assembly to call the ADO PIA via reflection on behalf of the ASP.NET page, the following would occur: 1. The reflection code sees the LinkDemand for full trust. 2. The Framework enforces the LinkDemand against the assembly in the GAC because it is the GAC’d assembly that is really making the method call. 3. The Framework converts the LinkDemand into a full demand because reflection is being used. 4. The Framework walks up the call stack, inspecting each assembly involved in the current call stack to see if it is fully trusted. 5. When the stack crawl reaches the partial trust page code the security check fails and a Securi- tyException is thrown. Keep this behavior in mind if you write a GAC’d wrapper assembly that calls a protected assembly on behalf of a partial trust ASP.NET application. The section on sandboxing titled “Sandboxing with Strongly Named Assemblies” will cover how a GAC’d assembly can ensure that it always has the necessary rights to call protected code, regardless of whether the call is made directly or via reflection. Working with the AllowPartiallyTrustedCallers Attribute You would be in a real quandary if there was no way to call protected code from a partial trust ASP.NET application. If you think about it, though, ASP.NET code is calling into what would technically be considered “protected code” all the time. Whenever you write a line of code that uses the Request or Response objects, you are accessing classes that live inside of SystemWeb.dll, which itself is installed in the GAC. However, in all the previous examples where sample code was writing information out using Response, there weren’t any unexpected security exceptions. The reason for this behavior is the AllowPartiallyTrustedCallersAttribute class located in the System.Security namespace. If an assembly author includes this attribute as part of the assembly’s metadata, when the .NET Framework sees a call being made from partially trusted code to the assembly, it does not trigger a LinkDemand for full trust. The System.Web.dll assembly uses AllowPartially​ TrustedCallersAttribute to allow partial trust code to call into its classes. You can see this if you 198 Download at Boykma.Com Chapter 4: A Matter of Trust run the ildasm utility (available in the SDK subdirectory inside of the Visual Studio install directory if you chose to install the SDK) against the System.Web.dll file located in the framework’s installation directory. You will see a line of metadata like the following if you look at the assembly’s manifest inside of ildasm. [mscorlib]System.Security.AllowPartiallyTrustedCallersAttribute::.ctor() If you are using assemblies that you don’t directly control or own, and you are wondering whether the assemblies can even be used in a partially trusted web application, you should ildasm them and look for the AllowPartiallyTrustedCallersAttribute. If the assemblies lack the attribute, then without additional work on your part (sandboxing the assemblies which is discussed later), you will not be able to install the code in the GAC and consume it directly from a partially trusted ASP.NET application. A few technical details about using AllowPartiallyTrustedCallersAttribute are listed here: ❑❑ Although you can add this attribute to any assembly, it makes sense to use it only with an assembly that is strongly named. ❑❑ Strongly named assemblies require a signing key and an extra step in the assembly’s build process to create the digital signature for the assembly’s code. You can set this all up in Visual Studio 2008 so that the work is done automatically for you. ❑❑ In ASP.NET 3.5 you can deploy strongly named assemblies either in the GAC or in the /bin directory of your application. Deploying a strongly named assembly in the /bin directory has some extra implications in partial trust ASP.NET applications. In the interest of brevity, folks frequently refer to the AllowPartiallyTrustedCallersAttribute as APTCA, or “app-ka” when talking about it. Trust me; it’s a lot faster to talk about APTCA rather than the full name of the attribute! To demonstrate using the attribute, create a really basic standalone assembly that is strongly named. The assembly exposes a dummy worker method just so there is something that you can call. C# public class SampleClass { public string DoSomething() { return “I did something”; } } VB.NET Public Class SampleClass Public Function DoSomething() As String Return “I did something” End Function End Class Download at Boykma.Com 199 Chapter 4: A Matter of Trust Initially, the assembly will be strongly named, but won’t have APTCA in its metadata. If you are wondering how to get Visual Studio to strongly name the assembly, just use the following steps: 1. Right-click the Project node in the Solution Explorer. 2. Select the Signing tab in the Property page that is displayed. 3. Check the Sign the assembly check box on the Signing property page. 4. If you are just creating a key file for a sample application like I am, choose New from the Choose a strong name key file drop-down list. In a secure development environment, though, you should delay sign the assembly and manage the private key information separately. 5. Type the key file name in the dialog box that pops up, and optionally choose to protect the file with a username and password. The end result is that when you build the standalone assembly, Visual Studio signs it for you. You can confirm this by running ildasm against the assembly. You will see the public key token, albeit with a different value, when you look at the assembly’s manifest: .publickey = (00 24 00 00 04 80 00 00 94 00 00 00 06 02 00 00 … ) Now you have a strongly named assembly and can start working with it from a partial trust ASP.NET application. First, install the assembly into the GAC using the gacutil tool: This tool is also available from the SDK directory. Run the following command to install the assembly into the GAC: “C:\..path..to..VS\SDK\v2.0\Bin\gacutil” -i SampleAPTCAAssembly.dll Next, you can try instantiating and calling the assembly from ASP.NET. Because I keep the standalone assembly in a separate project, I can’t use the project reference feature in Visual Studio. In a case like this, you can manually hook up a reference to any assembly located in the GAC by doing the following: 1. Navigate to %windir%\assembly to view the GAC. 2. Find your registered assembly in the list, and note the version number, culture, and public key token information. 3. Using that information, manually register the GAC’d assembly using the element in web.config. For the sample application, I added the following GAC reference into web.config: With this reference in the configuration, the sample application can reference the namespace from the assembly and use the sample class. 200 Download at Boykma.Com Chapter 4: A Matter of Trust C# using SampleAPTCAAssembly; … protected void Page_Load(object sender, EventArgs e) { SampleClass sc = new SampleClass(); Response.Write(sc.DoSomething()); } VB.NET Imports SampleAPTCAAssembly_vb.SampleAPTCAAssembly_vb … Protected Sub Page_Load( _ ByVal sender As Object, _ ByVal e As System.EventArgs) Handles Me.Load Dim sc As New SampleClass() Response.Write(sc.DoSomething()) End Sub Because the sample web application is set to run at Medium trust, running the sample page results in the following now familiar SecurityException: System.Security.SecurityException: That assembly does not allow partially trusted callers. However, armed with the information that the standalone assembly requires APTCA to be successfully called, this problem can quickly be rectified. Going back to the standalone assembly project, the APTCA attribute is added to the assembly by placing the attribute definition inside of the project’s AssemblyInfo.cs file. This file can be found by expanding the Properties node for the project inside of Solution Explorer. C# using System.Security; … //Allow partially trusted callers [assembly: AllowPartiallyTrustedCallers()] VB.NET Imports System.Security … ‘Allow partially trusted callers Recompiling the application and reinstalling the new assembly into the GAC gives you an assembly that will now allow a partial trust web application to call into it. Running the sample’s ASP.NET page in Medium trust succeeds, and the text from the standalone assembly is written out without triggering any exceptions. Download at Boykma.Com 201 Chapter 4: A Matter of Trust Sometimes changing GAC’d assemblies doesn’t seem to always take immediate effect. If you are sure that you have updated a GAC’d assembly with APTCA, and it still isn’t working, try closing down Visual Studio and running iisreset. Strong Named Assemblies, APTCA, and the Bin Directory One variation on the issue with APTCA and partial trust callers deals with the issue of deploying strongly named assemblies in /bin and then attempting to use them. You might think that you could create a strong named assembly for versioning purposes but then deploy it into the /bin directory of a web application for convenience. However, if you attempt to do this, the .NET Framework still enforces a LinkDemand when a partially trusted caller attempts to use a strong named assembly. You can see this if you take the standalone assembly used earlier and recompile it without APTCA. Drop it into the /bin directory of the web application (make sure to remove the old assembly from the GAC) and remove the GAC reference from web.config. Now when you run the sample web page it once again fails with a SecurityException. This behavior may take you by surprise if you have ASP.NET applications that formerly ran in full trust and that you are now attempting to tweak to get running in High trust or lower. If you have strongly named assemblies sitting in /bin (which admittedly in ASP.NET 1.1 you might have avoided because there were problems with loading strong named assemblies from bin), and if those assemblies never had APTCA applied to them, then your ASP.NET application will suddenly start throwing the familiar SecurityException complaining about partially trusted callers. This boils down to a simple rule: If you are creating strongly named assemblies, you should make the decision up front on whether the assemblies are intended to support partial trust environments like ASP.NET. If so, you should review the code to ensure that partially trusted applications are not allowed to call dangerous code (for example, a strong named assembly shouldn’t be just a proxy for directly calling random Win32 APIs), and then add the APTCA attribute to the assembly. For some developers who have large numbers of middle tier assemblies, quite a few assemblies may require this type of security review and the application of APTCA prior to being useable in a partial trust application. Another area where APTCA is enforced is for any type that ASP.NET dynamically loads on your behalf. Because you can create custom configuration section handlers, custom HttpModules, custom providers, and so on, ASP.NET is responsible for dynamically loading the assemblies that contain these custom extensions. Consider the following scenario: 1. An ASP.NET application runs in Medium trust. 2. You write a custom Membership provider in a strongly named standalone assembly. 3. The assembly isn’t attributed with APTCA. 4. For ease of deployment, you place the assembly in /bin. What happens? From a .NET Framework perspective, it triggers a LinkDemand for full trust when ASP.NET attempts to load the custom provider. Because it is ASP.NET that is loading the provider, the initial LinkDemand check succeeds. The provider loader code is buried somewhere in System.Web.dll, which itself sits in the GAC. So, from a .NET Framework perspective, everything is just fine with the 202 Download at Boykma.Com Chapter 4: A Matter of Trust immediate caller. Because ASP.NET dynamically loads providers with the System.Activator type, though, the Framework will continue to demand Full trust from all other code sitting in the calls stack. Because it is probably user code in a page that is making use of Membership in this scenario, the full stack walk to check for Full trust will end up failing. To give an example of this, you can use the standalone assembly from the earlier APTCA discussion, and add a simple Membership provider to it. C# public class DummyMembershipProvider : SqlMembershipProvider {} VB.NET Public Class DummyMembershipProvider Inherits SqlMembershipProvider End Class The assembly is again deployed into the /bin directory of the ASP.NET application. Because this is a Membership provider, the Membership feature must be configured to use the custom provider. A full strong type definition isn’t necessary, because the containing assembly is in /bin: A sample page that forces the Membership feature to initialize, and thus load all configured providers, is shown here: C# protected void Page_Load(object sender, EventArgs e) { Response.Write(Membership.ApplicationName); } VB.NET Protected Sub Page_Load( _ ByVal sender As Object, _ ByVal e As System.EventArgs) Handles Me.Load Response.Write(Membership.ApplicationName) End Sub Running this page at Medium trust results in a page failure: Description: An error occurred during the processing of a configuration file required to service this request. Please review the specific error details below and modify your configuration file appropriately. Parser Error Message: That assembly does not allow partially trusted callers. Download at Boykma.Com 203 Chapter 4: A Matter of Trust Depending on which piece of ASP.NET code is actually responsible for loading custom types, you will get different error messages. In this case, because loading custom Membership providers is considered part of the configuration for Membership, the error information is returned as an instance of System.Configuration.ConfigurationErrorsException. Again, this kind of failure can be solved by attributing the assembly with APTCA. After the assembly is updated with APTCA and redeployed to the /bin directory, the Medium trust application is able to load the custom provider. Now say that you instead make use of the GAC for a custom provider. The scenario looks like: 1. An ASP.NET application runs in Medium trust. 2. You write a custom Membership provider in a strongly named standalone assembly. 3. The assembly is not attributed with APTCA. 4. You deploy the provider in the GAC. In this case, ASP.NET adds an extra layer of enforcement. Before even attempting to spin up the provider with System.Activator, ASP.NET first checks to see of the provider’s assembly is attributed with APTCA. If ASP.NET cannot find the APTCA attribute, it immediately fails with a ConfigurationErrors​ Exception (though in this case the text of the error will be a bit different because it is ASP.NET’s APTCA check that is failing as opposed to the Framework’s APTCA enforcement). Although the provider case would still fail even if ASP.NET did not make this check (the page code in a partial trust web application would still be on the stack), there are other cases where ASP.NET dynamically loads code (for example, custom handlers and modules), and thus no user code exists on the stack. This is the main reason why ASP.NET adds its own additional APTCA check for dynamically loaded types that exist in GAC’d assemblies. All of this should serve to reinforce the fundamental tenet of strongly named assemblies: determine whether the strongly named assembly is intended for use in any type of partial trust scenario, and if so perform a security review and attribute with APTCA. Do not assume that you can “fake out” ASP.NET or the .NET Framework by using some level of indirection to get a reference to a strongly named type. Reflection will not help, because the Framework converts LinkDemands into full demands. In the case of ASP.NET, code that loads types from the GAC based on information in configuration explicitly looks for APTCA on an assembly before loading it on behalf of a partially trusted ASP.NET application. Sandboxing with Strongly Named Assemblies With an understanding of APTCA, the GAC, and partial trust callers under your belt, you can put the pieces together for wrapping code in a sandbox of sorts such that partially trusted callers can use more privileged code. The idea behind the sandbox is that a partial trust web application doesn’t require access to every possible API in the .NET Framework. For example, if you are developing a Medium trust web application that communicates with a database, chances are that the web application doesn’t really need to use every class in System.Data.SqlClient. Furthermore, it is likely that the web application does not require the ability to issue any arbitrary query. Instead, your web application probably has a very specific set of requirements—a specific set of tables and stored procedures that it should interact with. As a result, you could encapsulate this restricted functionality inside of an assembly (or assemblies) that exposes methods performing only the required query operations. With such an approach you have effectively created a sandbox within which your partial trust application can issue a limited set of SQL queries. 204 Download at Boykma.Com Chapter 4: A Matter of Trust Creating a sandbox assembly for use by a partial trust application requires the following: 1. A clear understanding of the specific functionality that needs to be publicly available to the par- tial trust application 2. Knowledge of the security expectations that the sandbox assembly can realistically demand from the partial trust code 3. Knowledge of the security requirements of lower-level code that the sandboxed assembly itself relies on Of the these three items, you can pretty easily scope out the requirements for point 1 because you would normally do this anyway in the course of designing and developing your web application. However, point 2 is something that you may not have given consideration to before. If you work on a development team where everyone knows who writes specific pieces of code, then you may not need to give too much though to the security expectations the sandbox assembly demands. You could instead author a sandbox assembly, install it on one or more web servers, and be done with it. However, if you write a sandboxed assembly for use by anonymous or unknown customers, then you should definitely enforce point 2. If you think about it, System.Web.dll could be considered a really, really big sandbox assembly. On behalf of millions of developers not personally known by the ASP.NET development team, the ASP.NET runtime is allowing partial trust web applications to do all sorts of interesting things. AspNetHosting​Permission, which was covered earlier, is the programmatic representation of a security requirement that ASP.NET demands from all partial trust applications. In the absence of a “personal trust” relationship, ASP.NET instead uses the custom permission to establish an understanding of the level of trust granted to a web application. As you saw, based upon that level of trust, ASP.NET will turn on and off various features. If you are planning on authoring a strongly named assembly, regardless of whether it goes in the GAC, you need to consider what types of permissions you expect (.demand) from calling code. Of course, another reason for doing this is that some code that calls into your assembly may be malicious code that is attempting to use your sandboxed assembly to subvert other security restrictions on the web server. In Figure 4-2, the general pattern of a sandboxed assembly requesting some type of permission from its caller is shown. For example, say that your strongly named assembly internally makes a request for a bank account balance lookup from some mainframe. The assembly exposes a public method for making this request that hides all of the internals necessary for setting up a call to a mainframe, parsing the response, authenticating the web server to the mainframe, and so on. In normal circumstances, your assembly is deployed on a web server, probably in the GAC, and the following call flow occurs: 1. The partially trusted web application calls a public method on your assembly, requesting the bank account balance lookup. 2. Rather than just blindly trusting the caller, your assembly requires that the web application has a custom permission defined by your company. It makes this check by constructing an instance of the custom permission and then programmatically demanding it. 3. Assuming that the web application has the required permission, your assembly makes the nec- essary calls into other privileged code to retrieve the bank account balance. Download at Boykma.Com 205 Chapter 4: A Matter of Trust Because of step 2, your sandboxed assembly is safer for use in partial trust applications and by any random and anonymous set of developers. Because your assembly requires a custom permission, the logical place to assign the permission to an ASP.NET application is in a custom trust policy file. Remember from earlier all of the permission classes that were registered with elements in a trust policy file? You could author your own permission that derives from System.Security.CodeAccess​ Permission and then configure it in the trust policy file and grant it in with element. Partially trusted caller Your strongly named assembly (2) Should request something in return (1) Calls a public method (3) Calls a privileged operation only if (2) succeeded Some lower level privileged operation Figure 4-2 Now a malicious user who obtains your sandboxed assembly and attempts to call it would need to overcome the following hurdles: ❑❑ They would need to obtain the assembly with the definition of the custom permission you are demanding. ❑❑ The custom permission would need to be installed in the GAC, but this requires machine administrator privileges. ❑❑ The trust policy file for the web application would need to be changed. Again, though, creating or editing trust policy files requires machine administrator privileges. 206 Download at Boykma.Com Chapter 4: A Matter of Trust Because the likelihood of compromising someone with machine administrator privileges is pretty low (if someone with machine admin privileges on your Internet-facing web farms has malicious intent, it’s all over!), any attempt by a partial trust web application to use your sandboxed assembly immediately fails when your assembly demands a custom permission. Always demand some kind of permission in your sandbox assemblies when you don’t know who is writing the partially trusted code that calls into your assembly. The last point mentioned earlier (step 3) noted that you also have to have an understanding of the security requirements of the code that your sandboxed assembly will call. This is necessary because it is likely that some of the classes you call also have their own demands. For example, if you were wrapping calls to System.Data.SqlClient, you know that the various classes in that namespace will demand SqlClientPermission. Even though your assembly is strongly named, and may be in the GAC, it doesn’t change the fact that the demand for SqliClientPermission will flow right up the call stack, and when the demand hits a partially trusted web application, the demand will fail. So, the third thing a sandboxed assembly may need to do is assert one or more permissions. When calling System.Data.SqlClient, your sandboxed assembly needs to assert SqliClientPermission. Doing so has the effect of stopping the stack walk for SqlClientPermission when your assembly is reached. Figure 4-3 shows this. Partially trusted caller Your strongly named assembly Asserts SqlClientPermission (2) Demand permission in return (1) Calls a public method (4) SqlConnection demands SqlClientPermission (5) The Assert satisfies the demand (3) Calls SqlConnection System.Data.SqlClient DFoiwgunrloea4d -a3t Boykma.Com 207 Chapter 4: A Matter of Trust Walking through the steps that occur: 1. The partial trust web application calls into the sandboxed assembly. 2. The sandboxed assembly demands permission from the partial trust web application rather than just immediately executing code on its behalf. 3. Assuming that the permission demand succeeds, the sandboxed assembly makes a call into ADO.NET. 4. ADO.NET demands SqlClientPermission, which starts a stack walk to check that all assem- blies in the current call stack have this permission. 5. When the stack walk “sees” that the sandboxed assembly asserted SqlClientPermission, the stack walk stops. 6. Control returns back to ADO.NET, and the appropriate method is allowed to execute. The need to demand some type of permission from the calling code is, hopefully, a little clearer now. Because sandbox assemblies may very well assert one or more permissions, it makes good sense to require some type of permission in return from the calling code. Think of this as the equivalent of giving your car keys to your teenager on the weekend (you are effectively asserting that you trust he or she will not do anything wrong with the car, but in return you expect your teenager to drive responsibly). There is one thing to keep in mind with the concept of asserting permissions. Even though any code can new() up a permission class and call the Assert method, this doesn’t necessarily mean that Assert will succeed. The reason a sandboxed assembly in the GAC can successfully call Assert for any permission class lies in the way the .NET Framework evaluates the Assert. When a piece of code calls Assert, the Framework looks at the assembly that contains the code making the assertion. Based on the evidence for that assembly (where is the assembly physically located, what is its digital signature, and so on), the Framework matches the assembly to the appropriate portion of the security policy currently in effect for that application domain. The Framework then looks for the asserted permission in the security policy; if the permission is found, the assertion succeeds. If the assertion fails, a SecurityException occurs. When assemblies are deployed in the GAC, code always has full trust, which means that GAC’d code can call any other code and use any of the functionality in the Framework. As a result, GAC’d code that calls Assert always succeeds. I won’t go into it here, but it is possible to structure the membership conditions for the .NET Framework’s security to allow code in other locations to also be assigned full trust. For most folks, though, installation in the GAC is the most straightforward way of obtaining full trust and, thus, being able to assert permissions. Sandboxed Access to ADODB Earlier in the section “Working with Different Trust Levels” a few samples attempted to use the old ADO data access technology from a partial trust web application. In this scenario, you can move the ADO data access code into its own sandbox assembly and then enable the assembly for use in partial trust. 208 Download at Boykma.Com Chapter 4: A Matter of Trust The sandbox assembly contains code that attempts to create a new recordset: C# public int CreateRecordset() { AspNetHostingPermission asp = new AspNetHostingPermission(AspNetHostingPermissionLevel.Medium); asp.Demand(); RecordsetClass rc = new RecordsetClass(); int fieldCount = rc.Fields.Count; return fieldCount; } VB.NET Public Function CreateRecordset() As Integer Dim asp As New AspNetHostingPermission( _ AspNetHostingPermissionLevel.Medium) asp.Demand() Dim rc As New RecordsetClass() Dim fieldCount As Integer = rc.Fields.Count Return fieldCount End Function The assembly is attributed with APTCA to allow partially trusted callers. The class also demands Medium trust from its callers. Because this method is working with ADO, which is effectively the precursor to ADO.NET, and ASP.NET grants SqlClientPermission at Medium trust, the CreateRecordset method works with ADO on behalf of any partially trusted caller running at Medium trust or higher. After installing the assembly into the GAC, the web application is updated so that it has a reference to the GAC’d assembly. The web page that uses the GAC’d assembly is shown here: C# using SampleAPTCAAssembly; … protected void Page_Load(object sender, EventArgs e) { ADODBWrapper wrapper = new ADODBWrapper(); Response.Write(wrapper.CreateRecordset().ToString()); } Download at Boykma.Com 209 Chapter 4: A Matter of Trust VB.NET Imports SampleAPTCAAssembly_vb.SampleAPTCAAssembly_vb … Protected Sub Page_Load( _ ByVal sender as Object, _ ByVal e As System.EventArgs) Handles Me.Load Dim wrapper As New ADODBWrapper() Response.Write(wrapper.CreateRecordset().ToString()) End Sub At this point the page still won’t work, because the COM interop layer for ADO is demanding FileIOPermission. However, because calling into a PIA means that you are calling into unmanaged code, the sandbox assembly also needs SecurityPermission to grant unmanaged code assert permission. It isn’t uncommon for sandbox assemblies to need to assert permissions to prevent demands in the underlying code from flowing up the call stack. To rectify the problem when calling the ADO PIA, the assembly asserts file IO permission and unmanaged code permission as shown here: C# //If we get this far, we trust the caller and are willing to assert //permissions on its behalf. PermissionSet ps = new PermissionSet(null); try { FileIOPermission fp = new FileIOPermission(PermissionState.Unrestricted); SecurityPermission sp = new SecurityPermission(SecurityPermissionFlag.UnmanagedCode); ps.AddPermission(fp); ps.AddPermission(sp); ps.Assert(); RecordsetClass rc = new RecordsetClass(); int fieldCount = rc.Fields.Count; return fieldCount; } finally { CodeAccessPermission.RevertAssert(); } VB.NET 210 ‘If we get this far, we trust the caller and are willing to assert ‘permissions on its behalf. Dim ps As New PermissionSet(PermissionState.Unrestricted) Try Dim fp As New FileIOPermission( _ PermissionState.Unrestricted) Dim sp As New SecurityPermission( _ SecurityPermissionFlag.UnmanagedCode) Download at Boykma.Com Chapter 4: A Matter of Trust ps.AddPermission(fp) ps.AddPermission(sp) ps.Assert() Dim rc As New RecordsetClass() Dim fieldCount As Integer = rc.Fields.Count Return fieldCount Finally CodeAccessPermission.RevertAssert() End Try In this example, two permissions were asserted: FileIOPermission and a SecurityPermission. However, you cannot create individual permission classes and then call Assert on each instance. When you call Assert, the Framework temporarily changes the security information associated with the current stack frame. At that point, you cannot Assert a second permission unless you tear down the first Assert. To get around this, use the class System.Security.PermissionSet to add one or more permissions to a permission set. You can then call Assert on the PermissionSet, and all the individual permissions that were added to the set are associated with the current stack frame. In the sample code, the PermissionSet allows the code to assert the file IO permission and the unmanaged code permission. When you need to assert permissions, you should try to assert only the specific permissions your code needs. The sample asserts unrestricted FileIOPermission, which technically states that the wrapper code may attempt any file IO operation anywhere on the file system. In this case, I don’t know specifically what file path (or paths) the COM interop layer is looking at, so I used PermissionState​ .Unrestricted. However, if the wrapper assembly is calling another piece of code that works with only a specific file or directory, it would be a better to assert FileIOPermission for only the required file or directory. All the example code is wrapped in a try/finally exception block. I did this to demonstrate how to call the static method CodeAccessPermission.RevertAssert. This isn’t strictly necessary when your code exits a method shortly after asserting permissions and doing some work (which is the case in the sample). However, if you have methods that need to briefly assert one or more permissions to call some other code, but your method then continues with other work, you should call RevertAssert to remove the extra security rights from the current stack frame. This call ensures that the remainder of the code in your method doesn’t inadvertently run with an elevated set of CAS permissions. At this point, if you run the sample ASP.NET page, everything finally works. To summarize, the following work is necessary to enable calling ADO from a Medium trust application: 1. Create a strongly named wrapper assembly. 2. Assign the APTCA attribute to the assembly to allow partial trust code like the web application to call into it. 3. Install the assembly in the GAC, thus allowing the assembly to assert any permission that it needs because GAC code is always fully trusted. 4. In the assembly, assert FileIOPermission and a SecurityPermission for unmanaged code to prevent the underlying COM interop demands from flowing up the call stack. Download at Boykma.Com 211 Chapter 4: A Matter of Trust Sandboxed Access to System.Data.SqlClient Access to some type of relational database is a common requirement for web applications, so this section describes what is involved in running queries against SQL Server for an application running in Low trust. Remember that the default trust policy file for Low trust doesn’t include the SqlClientPermission. Here, I reuse the assembly from the ADODB example because it already gets installed in the GAC and has the APTCA attribute applied to it. Because the new class in this assembly needs to prevent the demand for SqlClientPermission from making it to the user code running in the page, the new class needs to assert SqlClientPermission. As a basic protection though, the wrapper class requires at least Low trust from its callers. The code to do all this is: C# public class PubsDatabaseHelper { public DataSet RetrieveAuthorsTable() { //This class is only intended for use at Low trust or above (new AspNetHostingPermission(AspNetHostingPermissionLevel.Low)).Demand(); try { //Prevent SqlClientPermission demand from flowing up the call stack. SqlClientPermission scp = new SqlClientPermission(PermissionState.Unrestricted); scp.Assert(); string connectionString = @”server=.\SQL2005;integrated security=true;database=pubs”; using (SqlConnection conn = new SqlConnection(connectionString)) { SqlCommand cmd = new SqlCommand(“select * from authors”, conn); SqlDataAdapter da = new SqlDataAdapter(cmd); DataSet ds = new DataSet(“authors”); da.Fill(ds); return ds; } } finally { CodeAccessPermission.RevertAssert(); } } } 212 Download at Boykma.Com Chapter 4: A Matter of Trust VB.NET Public Class PubsDatabaseHelper Public Function RetrieveAuthorsTable() As DataSet ‘This class is only intended for use at Low trust or above CType(New AspNetHostingPermission(AspNetHostingPermissionLevel.Low), AspNetHostingPermission).Demand() Try ‘Prevent SqlClientPermission demand from flowing up the call stack. Dim scp As New SqlClientPermission(PermissionState.Unrestricted) scp.Assert() Dim connectionString As String = “server=.\SQL2005;integrated security=true;database=pubs” Using conn As New SqlConnection(connectionString) Dim cmd As New SqlCommand(“select * from authors”, conn) Dim da As New SqlDataAdapter(cmd) Dim ds As New DataSet(“authors”) da.Fill(ds) Return ds End Using Finally CodeAccessPermission.RevertAssert() End Try End Function End Class In the sample ASP.NET application, the trust level is reduced to Low. The page that uses the PubsDatabaseHelper has a GridView control on it, and some code in the page load event to programmatically data-bind the dataset returned from the PubsDatabaseHelper. C# using SampleAPTCAAssembly; … protected void Page_Load(object sender, EventArgs e) { PubsDatabaseHelper ph = new PubsDatabaseHelper(); grdView.DataSource = ph.RetrieveAuthorsTable(); grdView.DataBind(); } VB.NET Imports SampleAPTCAAssembly_vb.SampleAPTCAAssembly_vb … Public Sub Page_Load( _ Download at Boykma.Com 213 Chapter 4: A Matter of Trust ByVal sender As Object, _ ByVal e As System.EventArgs) Handles Me.Load Dim ph As New PubsDatabaseHelper() grdView.DataSource = ph.RetrieveAuthorsTable() grdView.DataBind() End Sub End Class When you run the sample page, it successfully calls the GAC’d sandbox assembly and populates the GridView control with the returned DataSet. This basic example of sandboxing ADO.NET access shows how the same techniques can be used for any arbitrary middle tier. Sandboxed assemblies are yet another reason why an architecturally sound middle tier is so important to web applications. Even if you are running all of your ASP.NET applications today in full trust, if you have a well-designed middle tier you’ve already taken the most important step toward enabling your web application for partial trust. The extra steps of security review, adding the APTCA attribute, and selectively asserting permissions are comparatively easy when there is already a clean separation of presentation layer and business layer code. ProcessRequestInApplicationTrust The last advanced topic that I want to cover is a security feature that was introduced in ASP.NET 2.0 and still exists in ASP.NET 3.5. There is a new attribute on the element called processRequest​ InApplicationTrust. By default, this attribute is set to true in the default trust level configuration: If you look at the root web.config file, you will not see the new attribute because the trust level configuration class internally defaults the attribute’s value to true. Because this attribute deals with trust-related security in ASP.NET, the attribute was added to the element. So, along with the ability to globally define the trust level for all applications on the machine, you can also globally control the value of the new attribute. However, unlike trust levels where there are valid reasons why you would want different trust levels for different applications, the setting for processRequestInApplicationTrust should be left alone at its default value of true. The attribute was introduced primarily to handle backwards compatibility issues when moving from ASP.NET 1.1 to 2.0. Because ASP.NET 2.0 tightens its enforcement of trust levels, some earlier applications and controls may fail with security exceptions when they run on ASP.NET 2.0 or ASP.NET 3.5. As a result, set the new attribute to false only when you encounter this kind of problem and even then after the applications or controls are tweaked to work in ASP.NET 2.0 and ASP.NET 3.5, you should revert to the default value of true for the attribute. 214 Download at Boykma.Com Chapter 4: A Matter of Trust The Interaction between Trust and ASP.NET Internal Code To get a better understanding of what the processRequestInApplicationTrust attribute really addresses, you need to understand a potential security issue for partial trust web applications. In several scenarios in ASP.NET, only trusted code is running on the stack. Probably the easiest example to explain is the no-compile page that was introduced in ASP.NET 2.0. A no-compile page has no user code in a code-behind file. Instead, the only code is the declarative markup in an .aspx. For example, the following page definition is an example of a no-compile page. C# <%@ Page Language=”C#” CompilationMode=”Never” %> Untitled Page
” ProviderName=”<%$ ConnectionStrings: pubsConnectionString.ProviderName %>” SelectCommand=”SELECT [au_id], [au_lname], [au_fname], [phone] FROM [authors]”>
Download at Boykma.Com 215 Chapter 4: A Matter of Trust VB.NET <%@ Page Language=”VB” CompilationMode=”Never” %> Untitled Page
” SelectCommand=”SELECT [au_id], [au_ lname], [au_fname], [phone] FROM [authors]”>
The page contains only a declarative representation of a GridView control bound to a SqlDataSource control. Furthermore, the page directive explicitly disallows compilation by specifying Compilation​ Mode=’Never’. If you run this page and then look in the Temporary ASP.NET Files directory, you will see that there is no auto-generated page assembly. When the page runs, ASP.NET effectively acts like a parsing engine, using the control declarations to decide which ASP.NET control classes to instantiate and then calling various methods on the instantiated controls. There is a potential security issue here because the call stack at the time the GridView is data-bound contains only ASP.NET code, and because all the ASP.NET code exists in the GAC, technically all of the code is running in full trust. The rough call stack at the time DataBind is called is listed as follows. Notice that every class involved in the call is fully trusted: ❑❑ SqlDataSource: Located in System.Web.dll. ❑❑ GridView: Located in System.Web.dll. ❑❑ Page: Located in System.Web.dll. 216 Download at Boykma.Com Chapter 4: A Matter of Trust ❑❑ HttpRuntime: Located in System.Web.dll. ❑❑ HostingEnvironment: Located in System.Web.dll. ❑❑ ISAPIRuntime: Located in System.Web.dll. ❑❑ Unmanaged code: Located in aspnet_isapi.dll. Clearly, if the only security check for no-compile pages was the demand for SqlClientPermission that comes from SqlDataSource calling into ADO.NET, a no-compile page would always succeed in calling into SQL Server. However, if you run the sample page in a Low trust application (because Low trust doesn’t have SqlClientPermission), you get a security-related exception. You can’t take advantage of no-compile pages to call privileged code, because ASP.NET restricts the page by forcing it to execute with the restrictions of the application’s current trust level. This is where the phrase “process request in application trust” comes from. Internally, when ASP.NET runs a nocompile page, it temporarily restricts the executing thread to the application’s trust level by calling PermitOnly on the NamedPermissionSet that was declared for the ASP.NET permission set in the trust policy file. So, not only does the trust policy file result in an application domain security policy, it also results in a reference to a NamedPermissionSet that ASP.NET can use. Calling PermitOnly tells the Framework that all subsequent method calls made on that thread should have CAS demands evaluated against only the permissions defined by the named permission set. As a result, on no-compile pages ASP.NET is effectively telling the Framework that ASP.NET’s GAC’d code should be treated as if it were regular user code that you wrote in a code-behind file. This behavior is all well and good for no-compile pages, and in fact there is no way for you to turn this behavior off for no-compile pages. Because no-compile pages are present since ASP.NET 2.0, there can’t be any backward-compatibility issues around trust level enforcement. However, in ASP.NET 1.1 you can write your own custom web controls, and if you choose you can sign them and deploy them in the GAC. Even though an ASP.NET 1.1 page auto-generates an assembly that is restricted by the application’s trust level, a GAC’d web control still has the freedom to run in full trust. That means in ASP.NET 1.1 it is possible to author a web control that asserts permissions and then calls into other protected assemblies despite the web control being placed on a page in a partially trusted web application. The reason for this loophole is that there are places when a Page is running where only ASP.NET code is on the stack, even for pages with code-behind and auto-generated page assemblies. The various internal lifecycle events (Init, Load, and so on) execute as part of the Page class, which is a GAC’d class. If the Page class constructs or initializes a control that in turn exists in the GAC, you have the problem where only fully trusted code sitting on the stack. ASP.NET 2.0, and consequently ASP.NET 3.5, tightens enforcement of trust levels by calling PermitOnly on the trust level’s PermissionSet just prior to starting the page lifecycle. The net result is that all activities that occur as a consequence of running a page, including management of each individual control’s lifecycle, are constrained to only those CAS permissions explicitly granted in the trust policy file. This enforcement occurs because the processRequestInApplicationTrust attribute on the configuration element is set to true by default. Hopefully, you now have a better understanding of why this setting should normally not be changed. However, if processRequestInApplicationTrust is set to false, then for compiled pages ASP.NET 2.0 and ASP.NET 3.5 will not call PermitOnly, and the loophole whereby GAC’d controls can avoid the application trust level still exists. Figure 4-4 shows two different call paths involving a GAC’d web control: one call path is the normal one; the other call path shows what occurs if “processRequestInApplication​ Trust” is set to false. Download at Boykma.Com 217 Chapter 4: A Matter of Trust (0) Assembles located in GAC run at full trust. (0) Application domain CAS policy established when the application domain started (5a) If PermitOnly is bypassed (2) Permission demand (5d) Check GAC ASP.NET pipeline code that CAS policy runs before the Page handler (5CbA)SCphoelcickyGAC Internal Page class logic processes controls in the declarative markup NamedPermissionSet.PermitOnly occurs if processPrequestInApplicationTrust = true SecurityException is thrown ! che(c4kbf)aIifls (4bap) pFdraommeawinoCrkAcShpeockliscy (3) ChepcoklicGyAC CAS (4a) Permission demand “sees” the PermitOnly Webcontrol that uses System.Data.SqlClient (1) Calls into (2) Permission System.Data.SqlClient classes demand SqlClientPermission hcao(s5deFe)ualGllwAtraCuy’sdst ADO.NET continues and runs the requested method demand (4a) If check succeeds Figure 4-4 0. When the application domain is initialized, the permissions in the trust policy file are applied as the application domain CAS policy. 1. A request for a page that contains a GAC’d web control occurs. When the web control’s Render method is called, it internally calls into System.Data.SqlClient classes. 2. This triggers a demand for SqlClientPermission. 3. The Framework first checks to see that the GAC’d web control has the necessary permission. Because the control is in the GAC, and thus running in full trust, the check succeeds. 4a. If processRequestInApplicationTrust is true, then when the permission demand flows up the call stack, it encounters the security restriction put in place by the Page class’s call to PermitOnly. 4b. The Framework now checks the set of permissions that were defined in the trust policy file, looking for SqlClientPermission. 4c. If the application is running in Medium or higher trust, the check succeeds, and the ADO.NET call eventually continues. 218 Download at Boykma.Com Chapter 4: A Matter of Trust 4d. If the application is running in Low or Minimal trust, the check fails, and a SecurityExcep- tion is thrown. 5a. If processRequestInApplicationTrust is false, the permission demand continues to flow up the call stack. 5b. The demand passes through various internal Page methods involved in instantiating the web control. Because the Page class is in the GAC, it runs at full trust and the demand succeeds. 5c. The demand eventually makes it to the top of the managed call stack. All code at this level is GAC’d ASP.NET code that was initially responsible for receiving the call from the ISAPI extension and starting up the HTTP pipeline. So again, the demand succeeds. 5d. Because only fully trusted code is in the current call stack, the demand succeeds, and the ADO. NET call eventually continues. To demonstrate how this actually works in code, you can create a simple web control that retrieves data from the pubs database in SQL Server and renders it on the page. C# public class MyCustomControl : WebControl { protected override void Render(System.Web.UI.HtmlTextWriter writer) { string connectionString = @”server=.\SQL2005;database=pubs;integrated security=true”; SqlConnection conn = new SqlConnection(connectionString); SqlCommand cmd = new SqlCommand(“select * from authors”, conn); DataSet ds = new DataSet(“foo”); SqlDataAdapter da = new SqlDataAdapter(cmd); da.Fill(ds); } } VB.NET Public Class MyCustomControl Inherits WebControl Protected Overrides Sub Render( _ ByVal writer As System.Web.UI.HtmlTextWriter) Dim connectionString As String = “server=.\SQL2005;database=pubs;” & _ “integrated security=true” Dim conn As New SqlConnection(connectionString) Dim cmd As New SqlCommand(“select * from authors”, conn) Dim ds As New DataSet(“foo”) Dim da As New SqlDataAdapter(cmd) da.Fill(ds) End Sub End Class Download at Boykma.Com 219 Chapter 4: A Matter of Trust The assembly is attributed with APTCA, signed with a signing key, and then installed in the GAC. In the web application, a reference is established to the GAC’d assembly. Notice that this GAC’d class does not assert SqlClientPermission. A page is created that uses the web control in the declarative markup of the page. C# <%@ Register TagPrefix=”GCW” Namespace=”GacdWebControl” Assembly=”GacdWebControl” %> .. other HTML snipped …
VB.NET <%@ Register Assembly=”GacdWebControl_vb, Version=1.0.0.0, Culture=neutral, PublicK eyToken=b2748bd5f288dfd2” Namespace=”GacdWebControl_vb.GacdWebControl_vb” TagPrefix=”cc1” %> .. other HTML snipped …
If you first run the page in Low trust, you receive a SecurityException due to the failed SqlClient​ Permission demand. The call stack that follows shows only trusted code on the stack because the code in the GAC’d web control is called as part of the Render processing for a Page. [SecurityException: Request failed.] ..snip.. System.Data.Common.DbConnectionOptions.DemandPermission() … System.Data.Common.DbDataAdapter.Fill(DataSet dataSet) GacdWebControl.MyCustomControl.Render(HtmlTextWriter writer) … System.Web.UI.Control.RenderControl(HtmlTextWriter writer) System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) … System.Web.UI.Page.ProcessRequest(HttpContext context) … 220 Download at Boykma.Com Chapter 4: A Matter of Trust Because PermitOnly occurs inside of the initial call to Page.ProcessRequest, when the SqlClientPermission demand reaches that point in the call stack, it fails and the GAC’d web control is not allowed to issue a command against SQL Server. Now change the level element, either in the root web.config or by overriding it in the application’s web.config, to the following: When you rerun the page, there is no longer a PermitOnly call restricting the permissions on the Page. Instead the SqlClientPermission demand flows up a call stack that consists of nothing but trusted code, and so the permission demand succeeds and the page successfully renders the dataset XML generated by the GAC’d web control. The best advice for the processRequestInApplicationTrust attribute on is to leave it at its default setting of true, and if at all possible also set the allowOverride attribute on the enclosing tag to false. This prevents enterprising developers from attempting an end run around the application trust level by way of a GAC’d control. However, if you do encounter applications being moved from ASP.NET 1.1 that run into problems with the new trust level enforcement in the Page class, you can temporarily set processRequestInApplicationTrust to false, but only for the specific application that requires the workaround. You should never disable the Page’s trust level enforcement for all applications on a machine, even though it is a little bit of a hassle, use application-specific elements or the application’s web.config instead to tweak the behavior for the offending applications. After you track down the problematic code and fix it (usually there are a few asserts necessary and a quick security review to make sure the asserts are appropriate), you can remove the level workaround for the application and revert to the intended ASP.NET 2.0 or ASP.NET 3.5 behavior. Summary In this chapter, you took a comprehensive look at the concept of code access security (CAS) in ASP.NET. Although the .NET Framework has a rich set of classes and configuration information for enforcing code access security, ASP.NET simplifies CAS by introducing the concept of a trust level. A trust level is represented as a piece of XML in a trust policy file that defines the set of .NET Framework permissions granted to an ASP.NET application. You can choose permissions for your application by using the configuration element and setting it to one of the following trust levels: ❑❑ Full: The web application can call any code in the Framework as well as Win32 APIs. ❑❑ High: The web application cannot call into Win32 APIs. Also, a default set of restricted permissions is defined by ASP.NET that gives your web application access to a reasonably large set of the Framework. ❑❑ Medium: The recommended trust level for hosting machines. Also recommended for any Internet-facing web server. Download at Boykma.Com 221 Chapter 4: A Matter of Trust ❑❑ Low: This trust level has a very limited set of CAS permissions. It is appropriate for applications that perform only local read-only operations. It is also used for applications that provide their own sandboxed execution model on top of ASP.NET such as SharePoint. ❑❑ Minimal: The lowest trust level available. It allows you to write only code that deals with inmemory data. Your web application can’t touch the file system or the network. Make your web applications more secure by at least moving from Full to High trust. Although doing so will likely require a few tweaks in your web applications and your business tiers, changing your applications so that they are only partially trusted is a major step in restricting the capabilities of malicious code. You can choose to customize the default trust levels by editing the policy files that ship with ASP.NET 3.5, or creating new custom trust levels and registering them inside a element. If you are writing an application in which you want to strictly limit the kind of code that can be called from the presentation layer, use a trust level (such as Low or Minimal) that grants very few permissions to application code. You can instead deploy your business logic inside of sandboxed assemblies that are deployed in the GAC and that expose only public APIs for a limited functionality set. Internally, your sandboxed assemblies need to assert various CAS permissions when calling other protected assemblies. Ideally, sandboxed assemblies should also demand some kind of permission from partially trusted applications prior to calling privileged code on behalf of the web application. 222 Download at Boykma.Com 5 Configuration System Security Many .NET Framework features depend on initialization information stored in various configuration files. ASP.NET especially is heavily dependent on configuration sections for defining the behavior of many aspects of the ASP.NET runtime. As a result the configuration information frequently contains sensitive information (usernames, passwords, connections strings, and so on). Configuration information can also directly affect the security settings enforced by certain features. As a result, configuration security is an important aspect of ensuring that a web application works as expected. This chapter covers the following aspects of securing configuration information: ❑❑ Using the element. ❑❑ Implementing granular inheritance control using the new “lock” attributes. ❑❑ Setting access rights to read and modify configuration. ❑❑ Managing IIS 7.0 configuration versus ASP.NET configuration. ❑❑ IIS 7.0 Feature Delegation. ❑❑ Implementing partial trust restrictions when using configuration. ❑❑ Using the new protected configuration feature. Using the Element The element has existed since ASP.NET 1.0 as a convenient way to define configuration inheritance without the need to create and deploy multiple separate configuration files. Because web applications always have some type of hierarchy, and thus the concept of Download at Boykma.Com Chapter 5: Configuration System Security configuration inheritance, you commonly need to define configuration settings at different levels of the ASP.NET inheritance hierarchy. The following list shows the ASP.NET 3.5 inheritance chain: 1. Settings defined in machine.config: In ASP.NET 2.0 many of the default ASP.NET settings have been moved out of machine.config to minimize startup time of non-web applications. 2. Settings defined in the root web.config: This new configuration file exists in %windir%\ Microsoft.NET\Framework\v2.0.50727\CONFIG. Most of the ASP.NET-specific default settings are now defined in the root web.config file. 3. Settings defined in the web.config file located in the root folder of a website: For the default web site, this would be a folder resembling c:\inetpub\wwwroot. 4. Settings defined in the root directory of the application: This is the web.config file that you normally work with in your applications. If the application is the website (meaning the application exists at “/”), the website configuration file and the application’s configuration file are one and the same. 5. Settings defined in a configuration file located in a subdirectory of a web application: Settings that can be changed on a per-directory basis can be placed in a web.config file in a directory. For example, you can define elements in web.config files that apply only to a specific virtual directory. Usually, you set some global defaults once in the machine.config and root web.config files, and spend most of your time editing the application’s web.config file. The contents of the element are the same configuration sections that you would normally set up inside of the various configuration files. Using the URL authorization section as an example, you could place the following into the web.config located at the root of a website (for example, at c:\inetpub\wwwroot\yourwebsite\web.config ) as follows: The element is interpreted as the beginning of a new virtual configuration file, meaning the element (or elements) nested immediately beneath the element must be top-level elements allowed in a normal configuration file. Thus, in the example just shown, the declaration is needed. You cannot place the element inside a element because it wouldn’t be allowed as a top-level element in a web.config file. The thing that becomes awkward with configuration inheritance is that you can quickly end up with a proliferation of .config files. For example, the URL authorization section () often requires many configuration files because the section can be applied down to the level of a specific web page. Developers who need to lock individual folders can drop a web.config file into each separate folder containing the folder-specific authorization rules. You saw an example of this back in Chapter 3 when URL authorization was covered. 224 Download at Boykma.Com Chapter 5: Configuration System Security You can determine how far down the inheritance chain a configuration section can be defined by looking at the section definitions. Most section definitions can be found within
elements up in machine.config. (Configuration section definitions are typically global to a machine so it makes sense to define them up in machine.config.) In a section definition like the following one:
…the allowDefinition attribute indicates that the health-monitoring configuration section can be defined all the way down to the web.config file for an application. So, you are not going to run into a problem with needing health-monitoring definitions for each your application’s subfolders. As a counterpoint, the URL authorization configuration section definition is:
The lack of the allowDefinition attribute for this configuration section is an indication that the authorization configuration can be redefined to any level of folder nesting. As a result, this configuration section is a good candidate for centralizing in an application’s web.config to prevent the number of folder-specific web.config files from growing out of control. Just looking at the section definition in machine.config is not always going to tell you whether the configuration makes sense at nested configuration levels. For example, the browser capabilities section can also be redefined at any level of the configuration hierarchy. Most likely, though, you would not redefine this section beneath the level of the application’s web.config. The Path Attribute The element is a way to control the number of .config files deployed for an application. The path attribute within the element tells the configuration system where in the configuration inheritance chain the information contained within the element should be applied. You can place a element inside of any configuration file within the inheritance chain, from machine.config all the way down to a configuration file in a subfolder of a web application, and then use the path attribute to indicate where the enclosed configuration information applies. Probably the most confusing aspect, though, of the element is the potential values for the path attribute. You can place the following values inside of the path attribute: ❑❑ A specific page (that is, default.aspx) ❑❑ A specific folder (that is, “subfolder” ) ❑❑ A combined path (that is, “subfolder/default.aspx” or “subfolderA/subfolderB”*. The name of a website as defined in IIS (that is, “Default Web Site” ) ❑❑ The combination of a website name and nested path information (that is, “Default Web Site/subfolderA”) With the path attribute, you can centralize configuration settings into a single physical configuration while still having the flexibility to define configuration settings for different applications, folders, pages, and so on. Download at Boykma.Com 225 Chapter 5: Configuration System Security Your decision about how to centralize configuration settings should be based on the relationship between the desired configuration information and the location of the configuration file. The root web.config file is an appropriate location for defining configuration information applicable to all web applications on a server. For example, this is the reason that the trust level configuration exists within a element in the root web.config file. The web.config file that can be placed at the root of an IIS website is probably used as an application configuration file by most developers. When you have no applications running at /, the website’s configuration file is an appropriate location for defining configuration information applicable to all applications running beneath the website’s root. Each application’s web.config file can be used for centralizing configuration information applicable to the application’s subfolders. Although you can spread out configuration information into configuration files in subfolders (as was shown in the URL authorization discussion in Chapter 3), it can be confusing to debug application problems. Unless someone who knows the application intimately realizes that configuration files are located in subfolders, you may end up scratching your head wondering why an application is behaving in a specific manner. Centralizing configuration information using tags in the application’s web.config file makes it easier for you to know exactly which configuration settings are in effect in different parts of the application. The allowOverride Attribute An additional level of security is available with the element through the allowOverride attribute. Commonly, a web server administrator defines some ASP.NET settings in machine.config. However, this wouldn’t be very useful if in each web application the developer simply redefined the configuration sections. The solution is to set the allowOverride attribute to false. After this is done, any attempt to redefine the configuration information contained within the element results in a configuration exception. If you globally define the trust level in machine.config as follows: …attempting to redefine this in your application’s web.config file results in an error page telling you that the parser encountered an error because the section has been locked down in a higher-level configuration file (in this case, machine.config). The amount of leverage the element plus the allowOverride attribute gives you is the reason security-sensitive configuration sections should be defined in either machine.config or the new root web.config file. Both of these files are also ACL’d on the file system to allow only write access by machine administrators so individual application developers can’t subvert the settings. Setting allowOverride to false guarantees the person who can change a locked configuration section is a member of the machine’s Administrator group. 226 Download at Boykma.Com Chapter 5: Configuration System Security Using the lockAttributes Around the time that Beta 1 of ASP.NET 2.0 was worked on, the development team came up with the idea of allowing the session state feature to lock portions of its configuration. The idea was to allow developers using session state to configure application-specific behavior such as the session timeout, while allowing machine administrators to define more global settings such as the session state mode and connection string. As part of this work, the team realized that the existing 1.0/1.1 -based lockdown approach was too restrictive. For instance, if an administrator wanted to enforce just the connection string used by all applications with SQL Server session state, an administrator would also have to drag in enforced settings for session timeout, cookieless support, and so on. On some web servers, this constraint might be reasonable, but in corporate hosting environments the likelihood is rather high that different internal corporate customers want different application-specific behavior. Rather than taking the early work for session state and limiting it to that feature, the concept of locking down individual configuration attributes as well as nested configuration elements was expanded and made available to any arbitrary configuration section. The following list describes the set of common attributes: ❑❑ lockAttributes: You can specify specific attributes on a configuration element that cannot be redefined lower down in the configuration hierarchy. ❑❑ lockElements: You can specify nested elements for a given configuration element that should not be redefined in child configuration files. This attribute is applicable only to complex configuration sections that contain nested elements. ❑❑ lockAllAttributesExcept: This is the companion attribute to lockAttributes. Depending on how many attributes you are locking down, it may be faster to lock all attributes except for a select few, rather than listing specific locked attributes with lockAttributes. ❑❑ lockAllElementsExcept: The companion attribute to lockElements. For complex configuration sections, it may be easier to define the nested elements that can be redefined, rather than list the locked elements with lockElements. Locking Attributes You can define the configuration for a feature in a higher-level configuration file and then selectively choose which attributes are allowed to be redefined in child configuration files. The lockAttributes and lockAllAttributesExcept attributes can be placed inside of any configuration element to limit the attributes that can be redefined in child configuration files. Take the Membership feature as an example of how you can lock individual attributes of a configuration element. The element has three attributes: defaultProvider, userIsOnline​ TimeWindow, and hashAlgorithmType. Of the three attributes, perhaps as an administrator you would like to ensure that any providers configured to use hashing should always use a stronger hashing variant, specifically SHA256. Download at Boykma.Com 227 Chapter 5: Configuration System Security To test the effect of locking the hashAlgorithmType attribute, you can write a sample application that defines the element in its web.config: The membership feature comes preconfigured in machine.config with just an empty element. However, for testing the attribute-based configuration lockdown, machine.config can be modified to look as follows: … You can see the hash algorithm that has been configured for the Membership feature by just outputting the setting on a web page in the sample application: Response.Write(Membership.HashAlgorithmType); The first time you run the sample application the redefined configuration in the application takes effect, and thus the output on the web page is “SHA1.” Now lock the settings in machine.config to prevent redefinition of the hashAlgorithmType attribute: Now when you attempt to run the sample application you get a configuration error stating that the hashAlgorithmType attribute has been locked in a higher-level configuration file. If you remove the hashAlgorithmType attribute from the application’s web.config file, the application runs successfully and the new hash algorithm is SHA256. Just for the heck of it, you can extend the attribute lock in machine.config to include the userIsOnlineTimeWindow and defaultProvider attributes as well: Use a comma or a semicolon to delimit the individual attributes defined in lockAttributes and lockAllAttributesExcept. This basic example with the element shows that lockAttributes gets pretty verbose. Locking something like the element with its 14 different attributes results in a lengthy definition for lockAttributes. Taking the section again as an example, allowing the userIsOnlineTimeWindow attribute to be changed in child configuration files, you could use the following more succinct machine.config definition: This construct allows you to redefine just a subset of the element in the application’s web.config file: As with the lockAttributes element, you can specify multiple attributes within lockAllAttributes​ Except. The comma and semicolon characters are also used as delimiters. 228 Download at Boykma.Com Chapter 5: Configuration System Security A shorthand for locking all attributes on a configuration element is to use an asterisk for the value of lockAttributes. The following example shows how to prevent the redefinition of any attribute on the element: Finding Out Which Elements Are Available for Lockdown To find out which elements are available for lockdown for a specific configuration element, you can create a bogus lockAttributes value. For example, with the following configuration definition (this is in machine.config, but the technique works in any configuration file): The error returned from ASP.NET is: The attribute ‘this doesn’t exist’ is not valid in the locked list for this section. The following attributes can be locked: ‘defaultProvider’, ‘userIsOnlineTimeWindow’, ‘hashAlgorithmType’. Multiple attributes may be listed separated by commas. Self-documenting errors are a good thing in this case! Although locking specific attribute configuration is a powerful feature of the configuration system, bear in mind that just because a lockdown is technically possible it may not always make much sense in practice. For example, the previous examples showing how to lock down the hash algorithm for the feature wouldn’t be useful if all membership providers used by an application were configured with reversible encryption instead. In this case, the configuration system happily enforces the attribute lockdown, but the end result would have no effect at runtime. This means attribute lockdowns (and element lockdowns discussed in the next section) still require you to look at the final runtime effect to determine whether the locked down configuration really makes sense. Locking Elements Because many configuration sections have nested elements, the configuration system provides the ability to lock elements within a configuration section. The lockElements and lockAllElementsExcept attributes control this behavior for any configuration section. For example, the section enables you to define providers using the element and , , and elements nested with the element. You could allow application developers to change attributes on the element but disallow them from changing any of the providers with the following configuration in machine.config: Attempting to make any changes to the element for in a child web.config file results in an error because the providers element has been locked in higher-level configuration file. Download at Boykma.Com 229 Chapter 5: Configuration System Security To allow an individual application to add new providers but disallow individual applications from removing or clearing providers defined in parent configuration files, your configuration in machine.config could look like the following: In this example, the lockAllElementsExcept attribute is used as a shortcut for allowing only child web.config files to use the element within the membership provider definition. A shorthand for locking all elements nested within a configuration element is to use an asterisk for the value of lockElements. The following example shows how to prevent the redefinition of any providers for the membership feature: The utility of element-based lockdown in Add-Remove-Clear (ARC) collections such as the membership provider collection is somewhat open to question. Locking by preventing changes to the element is for all practical purposes locking the configuration of the entire Membership feature. Because providers are central to the feature, using a -based lock would achieve about the same result. About the only benefit you gain from using lockElements with a feature like is that you could still allow individual applications to customize the online time window setting. A machine.config definition that allowed this would look as follows: However, some provider-based features like the health-monitoring benefit from the use of the elementbased lock. For example, as an administrator you could prevent removal or clearing of health-monitoring providers with the following configuration definition: With this definition, you can add providers to individual web applications. However, you cannot remove any providers defined in machine.config. This approach allows a box administrator to ensure that specific providers are always configured and in use on the machine for centralized web event collection, regardless of whatever other providers may be added by individual applications. 230 Download at Boykma.Com Chapter 5: Configuration System Security The following list describes the combinations of element-based locks that make sense for any AddRemove-Clear collection (provider definitions, the Profile properties definition, and so on): ❑❑ Lock all ARC elements to prevent child modifications by locking the parent collection element. This means putting a lockElements=’*’ definition in the parent element as was shown earlier (for example the element, the element for a feature like Profile, and so on). ❑❑ Allow individual applications to add elements to an ARC collection, but disallow changing any inherited collection elements. This means using a lock definition such as “lockAllElementsExcept=’add’ in the parent collection element. ❑❑ Allow individual applications to remove elements from an ARC collection, but disallow additions. This can be accomplished with a definition such as lockElements=’add’ in the parent collection element. This approach can be useful if you configure multiple providers on a machine, but leave it up to the individual applications to choose the specific ones to use. Individual applications can then remove the providers they don’t want to use. Although you can technically do other things, such as disallow but not , or vice versa, these types of locks are ineffective. The and elements are basically interchangeable. You can simulate a with a series of elements, so preventing a child configuration file from using but not is pointless. Similarly, preventing the use of but not is questionable because is just a fast way of removing all previously defined items in a configuration collection. Locking Provider Definitions Because a good chunk of this book is about Membership and Role Manager, you may be wondering how the attribute lock feature works with provider-based features. You may be thinking that with the attribute-based lock feature, you can customize portions of your provider definitions and restrict the redefinition of many of the provider attributes. To see which attributes in a provider element are lockable by default you can use the trick mentioned earlier. Take the sample application and create the following membership provider element: Download at Boykma.Com 231 Chapter 5: Configuration System Security The following error statement returns: The following attributes can be locked: ‘name’, ‘type’, ‘connectionStringName’, ‘enablePasswordRetrieval’, ‘enablePasswordReset’, ‘requiresQuestionAndAnswer’, ‘applicationName’, ‘requiresUniqueEmail’, ‘passwordFormat’, ‘description’. All provider definitions use the same underlying strongly typed configuration class (this is covered extensively in Chapter 10 on the Provider Model). The strongly typed provider configuration class defines only “name” and “type” as common provider attributes. Clearly, though, each provider-based feature has a rich set of feature-specific provider attributes, and the error message shown previously lists much more than the “name” and “type” attributes as available for lock. This behavior occurs because the strongly typed configuration class for the element includes a collection used to contain feature-specific provider attributes. When you place a lockAttributes or lockAllAttributesExcept attribute on a provider element, the configuration system considers the feature-specific provider attributes lockable along with the “name” and “type” attributes. (These two attributes are required on a provider definition, so they are always lockable). This still leaves the question as to how you actually lock a specific provider definition. Provider configuration always uses Add-Remove-Clear (ARC) collections, meaning that the provider definitions are built up through a series of elements, with optional and elements in child configuration sections. However, there is no such thing as a element. Without a modification element, what use are the locking attributes? If you define a provider with an element and then subsequently use and then add the provider in another configuration file, the configuration system remembers the original set of locked attributes from the first definition. It enforces the attribute lock when the provider is redefined. To see an example of this, you can define a membership provider in machine.config as follows: Then in the web.config for an application, you can redefine the provider as follows: If you attempt to run any pages in the sample application at this point, you end up with an error saying that the passwordFormat attribute was already defined and locked in a parent configuration file. Unfortunately, you can easily “fake out” the configuration system by using a element instead. If you substitute a element for the element, the web application will run without 232 Download at Boykma.Com Chapter 5: Configuration System Security a problem. Basically in ASP.NET 3.5 the configuration system lacks the “smarts” to retain attribute lock information when a element is used. Hopefully, in a future release of ASP.NET, this problem will be resolved. For ASP.NET 3.5, though, this means that you can only lockdown provider definitions with the following approaches: ❑❑ Use a tag to lock the entire provider-based feature. For example, configure the section in a parent configuration file and disallow any type of redefinition in child configuration files. ❑❑ Use the lockElements and lockAllElementsExcept attributes to control whether child configuration files are allowed to use the , , and elements. You might allow for child configuration files to add new provider definitions or you might allow child configuration files to remove previously defined providers. ❑❑ Use the lockElements=’providers’ attribute to prevent any kind of changes to the element, while still allowing child configuration files the leeway to change attributes on the feature’s configuration element (for example, allow edits to the attribute contained in or ). Managing IIS 7.0 Configuration versus ASP.NET Configuration Chapter 1 provided an overview of the new IIS 7.0 configuration system. There was a big step moving away from the old IIS 6.0 metabase configuration system into the new .NET-like configuration system. The structure and concept of the IIS 7.0 configuration system is based on the .NET Framework configuration system. They both make use of XML configuration files, proving the tight integration when it comes to mixing both configuration systems in a single file (for instance, the application’s web.config file, as you will see later in this section). The IIS 7.0 configuration system constitutes a hierarchy of XML configuration files that are distributed among the .NET Framework and IIS 7.0. This hierarchy includes ❑❑ The machine.config file that contains all the global settings and configurations for the .NET Framework. ❑❑ The root web.config configuration file that contains the .NET configurations in the web.config configuration file that was introduced since ASP.NET 2.0 as a way to delegate some of the configuration settings from the machine.config configuration file. ❑❑ The site’s web.config configuration file that contains specific configuration for a specific site. ❑❑ The application’s web.config configuration file that holds specific configuration for a specific application in the site. ❑❑ Finally, the site directory’s web.config configuration file that contains the most specific configuration settings for a directory within an application. In addition to the .NET Framework configuration files, there is the famous applicationHost.config file, which represents the overall configuration settings specific to IIS 7.0. Download at Boykma.Com 233 Chapter 5: Configuration System Security Figure 5-1 shows a graphical representation of the different configuration files that constitute the new IIS 7.0 configuration system. The components of the IIS 7.0 configuration system include the .NET Framework configuration files in addition to the IIS 7.0-specific configuration file. machine.config web.config (root) ApplicationHost.config web.config (%SystemDrive%/netpub/ wwwroot) web.config (%SystemDrive%/netpub/ wwwroot/MyApp) Figure 5-1 web.config (sub applications) As you already know by now, the applicationHost.config file is composed of two main section groups: and . The configuration section group holds global configuration settings used by the Windows Process Activation Service that apply to all Web sites hosted inside of IIS 7.0. Among the different global configuration sections are the configuration section group, which holds information about the different application pools created inside the IIS 7.0 engine, and the configuration section group, which holds information for every site and its inner applications installed on the IIS 7.0 web server. Such configuration settings are not allowed to be edited or modified on the site or application level; only web server administrators are allowed to modify these settings to apply to all sites and applications running on the machine. The other important configuration section group in the applicationHost.config file is the section group. This section contains a7l9l3t0h1e/ cCohn05fifgigu0r1atFi0o5n01s.eaitt7i/n3g0/s08fomrpthe IIS 7.0 Web server engine, including , , , and . The major difference between this configuration section group and the configuration section group is that IIS 7.0 allows application developers to play around with this section and modify entries inside it that best suits the application they are developing, taking into consideration the ability of the web server administrator to lock down certain sections inside the that he or she finds important for the functionality of the web server and that application developers should keep intact. Now that the preceding two major sections of the applicationHost.config configuration file have been introduced, you have a better understanding that the aforementioned IIS 7.0 configuration file contains global settings that only web server administrators have access to, and global/specific settings that can be defined globally on the IIS 7.0 web server level and can also be overridden by application developers. As mentioned previously, the new IIS 7.0 configuration system is a mixture of the .NET Framework configuration files and the IIS 7.0 applicationHost.config configuration file. Now the question arises: how does IIS 7.0 handle the distributed hierarchy of configuration files when an application is running and 234 Download at Boykma.Com Chapter 5: Configuration System Security executing inside IIS? What happens is that the IIS 7.0 configuration system has access to all the .NET Framework configuration files. At runtime the .NET Framework combines the different configuration sections from the different configuration files starting with the machine.config configuration file, adding to it any changes or editions made in the root web.config file, adding to it any changes or editions in the site web.config configuration file, and finally arriving at the application web.config file. In the case a subdirectory is accessed in an application, and then the web.config configuration file of that specific directory is also added to the combination of configuration sections. The .NET Framework piles up all the configuration settings into a single configuration file. At this time, the IIS 7.0 reads the .NET Framework joined configuration settings and more specifically the configuration section group. It ignores the rest of the ASP.NET configuration settings and reads the IIS 7.0-specific configuration section. This way, IIS 7.0 would have access to the ASP.NET configuration settings, just as ASP.NET itself has access to them, with one major difference: ASP.NET will be reading the ASP.NET-specific settings and ignoring the configuration section, and IIS 7.0 would ignore all ASP.NET related configuration sections and process the configuration section. Referring to the idea of delegation, IIS 7.0 should access and read the configuration section defined inside the ASP.NET piled-up configuration settings because, as you have read above, ASP.NET developers are given the privilege to configure IIS 7.0-specific settings from inside the different .NET Framework configuration files and, hence, if some IIS 7.0-specific settings were changed for a specific web application, the IIS 7.0 configuration system has to know about the changes so that it configures the application at the IIS 7.0 level with the changes or modifications required. From an architectural point of view, the IIS 7.0 configuration system inherits the structure of the .NET Framework configuration system. This led the IIS team at Microsoft to decide on grouping both configuration settings in the same configuration file. This improves portability and deployment, of course, since the developer has to move only the application’s web.config configuration file with the application when it is time to deploy it on a server. The IIS 7.0 installed on the production server will read all the specific IIS 7.0 configuration settings and customize the application’s specific configurations inside it. The only concern with such a design is the overlap between configuration settings in the sense that IIS 7.0 might be accessing and reading ASP.NET configuration sections and ASP.NET would be accessing the configuration sections, and hence problems and exceptions might occur. For this reason, the IIS 7.0 configuration system was designed to ignore the ASP.NET-specific configuration sections. In fact, the 2.0 .NET Framework’s machine.config configuration file, which is shared between ASP.NET 2.0 and ASP.NET 3.5, now has a new section called system.webServer that maps the configuration section to a handler that simply ignores that section as follows:
As you can see, the configuration section is mapped to the System .Configuration.IgnoreSection class, which simply ignores the IIS 7.0-specific configuration section when ASP.NET is reading the application’s compiled web.config configuration file. However, if you are running an application in ASP.NET 1.1 that is hosted inside an IIS 7.0 web server, you need to add the above section manually (since ASP.NET 1.1 existed a long time before IIS 7.0 came into the picture) so that no errors are generated when the application is running and executing. Download at Boykma.Com 235 Chapter 5: Configuration System Security Extending IIS 7.0 with Managed Modules and Handlers Now that you understand the IIS 7.0 configuration system and its general architectural design, it is important to discuss some the changes that were made on some configuration sections, especially the and configuration sections that were originally located in the configuration section group of the .NET Framework configuration files. As you already know, IIS 7.0 introduced the Integrated mode of execution (explained in Chapter 2). With the new Integrated mode, ASP.NET developers can now build their custom handlers and modules in .NET and make them participate in the processing of HTTP requests that go into the unified HTTP request pipeline. At every stage in the unified HTTP request pipeline, the IIS 7.0 core engine checks to see if there are any native and managed modules to initialize using an internal native module that takes care of querying the managed modules to see whether there are managed modules registered to run in the Integrated mode. Because IIS 7.0 has no clue about any configuration sections defined inside the ASP.NET specific configuration sections, it has no way of knowing which managed modules and handlers are registered inside the ASP.NET configuration section group that need to run and execute when the web application is executing in the IIS 7.0 Integrated mode. The only way for the IIS 7.0 configuration system to know which managed modules and handlers the application developer has attached to the running application is to query the configuration section that the application developer used to register the managed modules and handlers to run in the Web application that is configured in the Integrated mode. In other words, the configuration section is the only interface ASP.NET developers have inside the application’s web.config configuration file to interact with the IIS 7.0 configuration system. Of course, administrators and developers can use the IIS 7.0 Manager tool to do all the configuration settings for the application, but this privilege is not always available for developers hosting their application in remote servers, so configuration section is their only way out. Therefore, the and configuration sections located inside the configuration section group have no effect when the application is running in the IIS 7.0 Integrated mode—thus, the need was to move the aforementioned configuration sections inside the configuration section so that IIS 7.0 can understand which managed modules and handlers can be used to extend its functionality. Currently, the configuration section includes the and configuration section groups that you can use to configure your managed modules and handlers that are to be run inside the unified HTTP request pipeline. Managing the Native versus Managed Configuration Systems Reading about the IIS 7.0 configuration system might bring fears and worries to you on how to manage both configuration systems and what to count on. In this context there is no magical solution that you can follow when it comes to managing both configuration systems. At the same time, having flexibility 236 Download at Boykma.Com Chapter 5: Configuration System Security and richness with both configuration systems should be a source of power for you as a developer, giving you more control in configuring both the IIS 7.0 and ASP.NET specific features. For instance, IIS 7.0 contains the CustomErrorModule, a native module running in the IIS 7.0 Web server core engine that maps to the configuration section that allows you to define custom HTML/ASPX pages to handle specific errors that might occur during the processing of HTTP requests inside IIS 7.0. The configuration section, for example, looks something similar to the following: The preceding configuration section configures a possible error through its statusCode attribute and maps its handler to an .htm static page. In your application, you can map to a custom .aspx page that is part of the theme and layout of the application you are developing. Another flexibility is given to you through the native UrlAuthorizationModule that ships as part of the IIS 7.0 and is configured inside the configuration section. Now developers have the option of either using the ASP.NET UrlAuthorizationModule or the IIS 7.0 UrlAuthorization​ Module by simply editing the application’s web.config configuration file for either the ASP.NET or IIS 7.0-specific configuration sections. By default, the native module works automatically with both native and managed requests; however, the managed module has to be removed from the section inside the application’s web.config configuration file and then added again with the precondition attribute set to an empty string, a trick you have learned about before. Nevertheless, the richness of the authorization feature and its ease of configuration inside either the or give you a powerful way of authorizing your users. In addition to the previous features introduced with IIS 7.0 is the output caching module. The caching module is defined in the applicationHost.config as follows: And it is configured through the following configuration section: This native module caches an application’s output in the kernel mode cache, thereby reducing the application’s response time. Once again you are given the option of configuring output caching either with the native IIS 7.0 module or through the managed output caching module in ASP.NET, the OutputCacheModule represented by the configuration section. You have witnessed above the new native and configurable modules that are introduced by IIS 7.0. All of the above modules are easily configured through the flexible IIS 7.0 configuration system. However, you can clearly see the overlapping in functionalities between the features that have already existed in ASP.NET and the ones introduced with IIS 7.0. Download at Boykma.Com 237 Chapter 5: Configuration System Security A general recommendation for the above overlapping configurable native and managed modules is to continue using ASP.NET custom errors for ASP.NET content because the custom errors feature in ASP. NET is tied directly into ASP.NET’s logic for dealing with unhandled exceptions. Regarding authorization, once again the recommendation is to use the native UrlAuthorizationModule with any new project you start developing for the main reason that this native module works fine with both native and managed requests without any modifications. However, for current applications that are already configured with the managed UrlAuthorizationModule, keep using the managed module when the application gets upgraded to run under IIS 7.0. Finally, regarding the output caching feature that is present in both ASP.NET and IIS 7.0, originally output caching in IIS 7 is intended for classic ASP applications as opposed to ASP.NET content. As a result, for anything but the most trivial ASP.NET caching scenarios, you are better off sticking with ASP.NET’s output caching. IIS 7.0 Feature Delegation IIS 7.0 provides a new feature that gives administrators a visual tool to decide which configuration sections in the ApplicationHost.config file can be configured on the application level. By default most of the configuration sections in the ApplicationHost.config file are locked down, meaning that applications hosted on the IIS web server cannot re-configure those locked-down configuration sections in the application’s web.config file. The main two configuration section groups in the Application​ Host.config configuration file are the and the configuration section groups:
238 Download at Boykma.Com Chapter 5: Configuration System Security
The configuration section group is a global section that is usually configured by administrators and cannot be edited by specific applications or virtual directories. However, the configuration section group is the section that developers can override through the application’s web.config configuration file. Figure 5-2 shows the Feature Delegation applet when opened in IIS 7.0 Manager. Figure 5-2 Download at Boykma.Com 239 Chapter 5: Configuration System Security You can see that some of the managed and native modules are listed with either Read Only or Read/Write. Read-Only delegation for a feature means that applications can only read the configuration settings set for that specific feature in the ApplicationHost.config configuration file and cannot change any settings. For example, the Error Pages module has a Read-Only delegation that means no application can change or configure the error pages’ global settings. On the other hand, when a feature has a Read/Write delegation, it means that an application’s web.config file can read and change the default or global configuration settings. An example is the Authentication – Anonymous feature configured with Read/Write delegation. Having a Read/Write delegation allows an application’s web.config file to configure the AnonymousAuthenticationModule. For instance, you can now disable the AnonymousAuthentication native module by adding the following configuration section in the application’s web.config file: If you try to configure a locked-down module inside the application’s web.config file, you receive the following exception: This configuration section cannot be used at this path. This happens when the section is locked at a parent level. Locking is either by default (overrideModeDefault=”Deny”), or set explicitly by a location tag with overrideMode=”Deny” or the legacy allowOverride=”false”. The exception message is clear enough to inform you that you are trying to configure a locked-down configuration section at the ApplicationHost.config file. When you set the delegation for a feature to Read Only, a new section is added at the end of the ApplicationHost.config configuration file. This section has a Path attribute that is used to specify the path to a specific application or if left as empty string meaning that the section applies to the entire applications hosted on the web server. The other attribute this section has is the overrideMode attribute that is set to Deny. Setting this attribute to Deny means that the configuration sections listed inside the configuration section are locked down and applications hosted on the web server can neither configure the listed configuration sections nor change any of them. If, on the other hand, the value of the overrideMode is Allow, the subconfiguration sections listed are allowed to be changed and modified by the hosted application’s web.config files. It is recommended to unlock configuration sections by using a configuration section whether you are unlocking manually or by using the Feature Delegation in IIS 7.0. For instance, consider that the configuration section is locked down in the ApplicationHost.config file as follows:
To unlock the above native module configuration section so that an application’s web.config file can change the default documents assigned to an application, you could either use the Feature Delegation to 240 Download at Boykma.Com Chapter 5: Configuration System Security unlock it or do it manually. Unlocking sections manually or through Feature Delegation results in the same section being added to the ApplicationHost.config file, as follows: The new section applies to all the applications hosted on the IIS web server since the Path attribute has the value of an empty string. The Allow value of the overrideMode attribute means that any subconfiguration section is automatically unlocked so that applications can re-configure it using the configuration section group in the web.config file. You can notice the presence of the configuration section group in the section above. To unlock any configuration section, you should place it inside the configuration section group since an application’s web.config file uses the configuration section group to configure IIS features. Hence, placing the features’ sections you want to unlock inside this configuration section group means that those features are unlocked and can be re-configured inside the same configuration section of the application’s web.config file. The configuration section has been newly added into the IIS 7.0 application​ Host.config file. Having this configuration section as part of the applicationHost.config configuration file gives the web server administrator a way to define IIS features globally. In addition, developers can add this section into the application’s web.config file to customize the IIS features per the current web application. Once you have unlocked the configuration section using the Feature Delegation, you will configure the default documents for a hosted application. Figure 5-3 shows the Default Document applet in IIS 7.0 that can be used to edit the default documents in an application. Any changes you make here are reflected into the application’s web.config file. For example, only the default.aspx page has been added to the list of default documents; therefore, you would expect the configuration section group to look like the following: Download at Boykma.Com 241 Chapter 5: Configuration System Security Figure 5-3 As you can see in the preceding configuration settings, all the preset default documents have been cleared out and only one web page has been added: the default.aspx. Without having an administrator add a new configuration section to allow modifying of the above native module, you would not have been able to change or configure this section. This applies to all the sections located inside the configuration section group located in the ApplicationHost. config configuration file. Using IIS 7.0 Feature Delegation provides administrators with an easy way to control which configuration sections are allowed to be re-configured and overridden by an application’s web.config files and which sections are not allowed. It is through this feature that IIS 7.0 protects the configuration sections inside the ApplicationHost.config from being edited or changed by applications as a way to protect the global settings set by administrators on web servers. Moreover, this feature gives developers an easier way of configuring the web server from their application’s web.config file without the need to contact the web server administrator or connect to the web server. However, this depends on how much configuration sections are unlocked by administrators taking into consideration security attacks and other safety measures. In addition to using the configuration section to lock and unlock features, you can lock specific elements and attributes the same way as you have read about at the beginning of this chapter. For instance, if you want to enable the native WindowsAuthenticationModule at the Application​ Host.config file level and prevent the application from editing the value of the enabled attribute, you would add something such as: 242 Download at Boykma.Com Chapter 5: Configuration System Security As you can see, the configuration settings use the same lockAttributes attribute to lock down specific attributes. To lock all the attributes of a feature, simply set the value of the lockAttributes to the list of attributes, separated by a comma (,). You can also lock elements for any feature. For instance, suppose you want to enable the native WindowsAuthenticationModule to all hosted applications on the web server but lock the Providers element from being edited by specific applications. The following configuration settings make sure that the Providers element will not be touched by any hosted application: In addition to being able to lock down specific elements, you can also lock down multiple elements by simply setting the value of the lockElements to a comma-separated list of all the elements to be locked down. If you want to lock down all elements or attributes except a single element or attribute, you can make use of the lockAllElementsExcept or lockAllAttributesExcept to list the only unlocked element or attribute for a feature while locking down the rest of elements or attributes that belong to a feature. As you can see, the same locking sections and attributes that were used and explained above for ASP.NET apply to the configuration sections of IIS 7.0. In fact, the IIS team imported the concepts and ideas from the ASP.NET configuration system and applied them on the IIS 7.0 configuration system, which is why you see so many similarities between the ASP.NET and IIS 7.0 configuration systems. The locking by elements and attributes will not be explained further since the same concepts that apply to ASP.NET and that were explained above apply to IIS 7.0 configuration system locking features. Download at Boykma.Com 243 Chapter 5: Configuration System Security Reading and Writing Configuration Before diving into specifics on ACL requirements for reading and writing configuration, a quick primer on using the strongly typed configuration API is useful. Even though a detailed discussion of the new strongly typed configuration API is out of the scope of this book, it is helpful for you to understand the basic coding approaches for manipulating configuration before you see the various security requirements that are enforced when using these APIs. You may never end up using the strongly typed configuration API. For example, if you use the Membership feature, almost all of the configuration information about the feature itself (the configuration element) and the individual providers (the various elements) are available from the Membership and various MembershipProvider-derived classes. Other features like Forms Authentication follow a similar approach. However, some features, such as session state, don’t mirror every configuration setting via a property from a well-known feature class. Also for administrative-style applications, it makes sense to deal with configuration information using the configuration APIs as opposed to using different feature classes that are potentially scattered through different namespaces. Reading configuration for a web application can be accomplished in two different ways. If you want to use the configuration APIs available to all Framework applications, you use the ConfigurationManager class, as shown here: C# … using System.Web.Configuration; using System.Configuration; … protected void Page_Load(object sender, EventArgs e) { SessionStateSection sts = (SessionStateSection) ConfigurationManager.GetSection(“system.web/sessionState”); Response.Write(“The session state mode is: “ + sts.Mode.ToString() + “
”); } VB.NET … Imports System.Configuration Imports System.Web.Configuration … Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) Handles _ Me.Load Dim sts As SessionStateSection = _ CType(ConfigurationManager.GetSection(“system.web/sessionState”), _ SessionStateSection) Response.Write(“The session state mode is: “ & _ sts.Mode.ToString() & “
”) 244 End Sub Download at Boykma.Com Chapter 5: Configuration System Security The ConfigurationManager class has a static GetSection method that you can use to obtain a reference to a strongly typed configuration class representing a configuration section. You tell the ConfigurationManager which section you want by specifying an XPath-like syntax to the configuration section you want. Because in this case the sample is showing how to access the configuration information for the session state configuration information, and this configuration section is nested within the configuration section, the path that you pass is system.web/sessionState. The path information is case-sensitive because configuration files are XML files. After ConfigurationManager finds the section, you cast the returned object to the correct type. ASP.NET includes several strongly typed configuration section classes within the System.Web.Configuration namespace. In the sample code you cast to an instance of SessionStateSection, which is the strongly typed configuration class used for the Session State feature. With the reference to SessionStateSection in hand, you can access any properties exposed by the class; the sample uses the Mode property to write the session state mode for the current application. The ConfigurationManager class is scoped only to the current application, though, so it is not flexible enough for applications that need to edit arbitrary configuration files for different web applications. As a result, there is a companion configuration class called WebConfigurationManager, which includes additional overloads for its methods to allow loading of arbitrary web application configuration files. C# … using System.Web.Configuration; using System.Configuration; … protected void Page_Load(object sender, EventArgs e) { MembershipSection ms = (MembershipSection) WebConfigurationManager.GetSection(“system.web/membership”, “~/web.config”); Response.Write(“The default provider as set in config is: “ + ms.DefaultProvider + “
”); } VB.NET … Imports System.Configuration Imports System.Web.Configuration … Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) _ Handles Me.Load() Dim ms As MembershipSection = _ CType(WebConfigurationManager.GetSection(“system.web/membership”, _ “~/web.config”), MembershipSection) Response.Write(“The default provider as set in config is: “ _ & ms.DefaultProvider & “
”) Download at Boykma.Com 245 Chapter 5: Configuration System Security In this sample, the GetSection method includes a second parameter specifying the virtual path to the current application’s web.config file. You can change the value of this parameter to point at other web application configuration files, or at configuration files located in subdirectories within a web application. Various overloads let you use physical file paths as well as virtual file paths when referencing configuration files. Writing to configuration requires that you actually open the entire configuration file, as opposed to just getting a reference to an individual configuration section. This returns a reference to an instance of the System.Configuration.Configuration class. (It’s not a typo; the class that represents a configuration file is really called Configuration within the System.Configuration namespace.) As with read operations, you can use the ConfigurationManager or the WebConfigurationManager to accomplish this. However, the available methods on the ConfigurationManager are not intuitive from the perspective of a web application developer because the various overloads refer to variations of configuration files for client executables. As a result, you will probably find the WebConfigurationM​anager makes more sense when you edit web.config for your web applications. After you programmatically open a configuration file, you get a reference to the specific configuration section you want to edit from the Configuration instance. You can set various properties on the strongly typed configuration section as well as manipulate any writable collections exposed on the configuration class. After all the edits are made, you call the Save method on the Configuration instance to commit the changes to disk. The following code demonstrates using the WebConfigurationManager to load and update a configuration section. C# … using System.Web.Configuration; … protected void Page_Load(object sender, EventArgs e) { Configuration config = WebConfigurationManager.OpenWebConfiguration(“~”); MembershipSection ms = (MembershipSection)config.GetSection(“system.web/membership”); ms.DefaultProvider = “someOtherProvider”; config.Save(); } VB.NET … Imports System.Web.Configuration Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) _ Handles Me.Load Dim config As Configuration = WebConfigurationManager.OpenWebConfiguration(“~”) Dim ms As MembershipSection = _ CType(config.GetSection(“system.web/membership”), _ MembershipSection) 246 Download at Boykma.Com Chapter 5: Configuration System Security ms.DefaultProvider = “someOtherProvider” config.Save() End Sub Several overloads to the OpenWebConfiguration method allow you to specify the exact configuration file you want to open for editing. As shown in the sample, the “~” shorthand can be used for loading the current application’s web.config file. The configuration system does not enforce any kind of concurrency or locking if multiple threads attempt to update the same configuration file. For this reason, you should ensure that any code that edits configuration files serializes access to the configuration file, or is written to handle the exception that is returned from the configuration system if it detects that changes occurred to the underlying configuration file. If you write console applications for editing configuration files, you probably won’t run into this issue. However, an administrative website that allows editing of any web.config file located on a web server should be written with concurrency in mind. Permissions Required for Reading Local Configuration The most common scenario is reading configuration information for a web application located on the same server as the code that performing the read operation. For example, each time a web application starts up, ASP.NET is reading configuration information down the entire inheritance chain of configuration files. Furthermore, as you use various features, such as Membership, Role Manager, Session State, and so on, your code triggers additional reads to occur from the various configuration files. As mentioned in Chapter 2, when an application domain first starts up, the identity that is used is either the process identity or the application impersonation identity. So under normal conditions, the Read ACL on web directories that is granted to IIS_USRS allows the default process identity to read configuration information. Looking up the configuration inheritance chain, the default ACLs on the various configuration files are: ❑❑ The web application’s directory grants Read access to IIS_USRS, so IIS_USRS has Read access to the application’s web.config file. ❑❑ The root web.config file located at %windir%\Microsoft.NET\Framework\v2.0.XYZ\​ CONFIG\w​eb.config grants Read access to IIS_USRS. ❑❑ The machine.config located in the same CONFIG subdirectory also grants Read access to IIS_USRS. This set of ACLs allows the configuration system to merge configuration sections up the inheritance chain. If you remove these Read ACLs from any one of these configuration files, ASP.NET would be unable to Read configuration during application startup and your web application will fail to start. Either the process identity or the application impersonation identity is also used when reading configuration information during normal runtime processing, specifically when using the GetSection method on WebConfigurationManager or ConfigurationManager. For example, if you use Windows authentication in a web application and enable client impersonation, even if the impersonated account does not have access to read the application’s web.config file, the web application still runs and configuration information is still successfully read. Download at Boykma.Com 247 Chapter 5: Configuration System Security If you think about it, this behavior makes sense. It would be a pretty onerous security requirement if every possible Windows user of an application with client impersonation turned on was required to have Read access up the configuration inheritance chain. Although the default ACLs on the CONFIG subdirectory do grant Read access to the local Users group (and hence any authenticated user on the machine has Read access), it is not uncommon to remove this ACL on hardened servers. The GetSection call succeeds because GetSection is considered to be a “runtime” configuration API. When you call GetSection the configuration system accesses cached configuration information that was previously loaded while running as either the process identity or the application impersonation identity. From a runtime perspective, loading configuration information is a service that the configuration system provides to running code. This behavior becomes clearer when you compare the difference between the runtime configuration API and the design-time configuration API. Earlier you saw that an alternative approach for getting a configuration section was to use a method such as WebConfigurationManager.OpenWebConfiguration or ConfigurationManager.OpenExeConfiguration. These Open* methods are considered “design-time” configuration APIs. As a result, they have different security semantics when accessing configuration information. When you call an Open* method the configuration system attempts to open one or more physical configuration files on disk. For example, if you attempt to open a web application’s configuration, a file open attempt will occur up the entire inheritance chain of configuration files. These file open operations are like any other call to the File.Open method. The security token on the operating system thread must have Read access to one or more configuration files. If you have a web application using Windows authentication with client impersonation enabled, and you write the following line of code: Configuration config = WebConfigurationManager.OpenWebConfiguration(“~”); … the open attempt will fail unless the impersonated client identity has Read access to the application’s web.config as well as the root web.config and machine.config files located in the Framework’s CONFIG subdirectory. You can see this behavior if you add an explicit Deny ACE to the application’s web.config that disallows Read access to the application’s web.config. The call to Open​ WebConfiguration will fail with an Access Denied error. You will have the same failure if you add a Deny ACE on the root web.config or on machine.config. However, if you change your code to call WebConfigurationManager.GetSection, your code will run without a problem. The following list summarizes the security requirements for the runtime and design-time configuration APIs: ❑❑ GetSection: Regardless of whether this is called from WebConfigurationManager or ConfigurationManager, the process identity or the application impersonation identity (if application impersonation is being used) requires Read access to the application’s web.config file, the root web.config file and the machine.config file. If you are attempting to read configuration at a path below the level of the root of a web application, Read access is also required on the lower-level configuration files. This level of access will normally exist because without it the web application would fail to start up. ❑❑ GetWebApplicationSection: This is just another variation of GetSection available on WebConfigurationManager. It has the same security requirements as GetSection. 248 Download at Boykma.Com Chapter 5: Configuration System Security ❑❑ OpenWebConfiguration: This method is available only on WebConfigurationManager. The operating system thread identity at the time the call is made requires Read access to the application’s web.config file, the root web.config file and the machine.config file. If you are attempting to read configuration at a path below the level of the root of a web application, the operating system thread identity also requires Read access to the lower-level configuration files. ❑❑ Other Open* methods: Both WebConfigurationManager and ConfigurationManager have a variety of methods starting with Open that provide different overloads for opening configuration files at different levels of the inheritance chain (that is, open just machine.config) as well as different ways for referencing virtual directories in a web application. No matter which Open* method you use, the operating system thread identity requires Read access to all configuration files that contribute to the configuration for the desired application or virtual path. When only machine.config is being opened, Read access is required only on machine.config because the lower-level configuration files will not be opened (for example, root web.config and application-specific configuration files have no effect on determining machine-level configuration information). Permissions Required for Writing Local Configuration Writing configuration is not something that a web application would normally attempt. Hence, the default ACLs up the configuration hierarchy don’t grant any Write access to commonly used ASP.NET accounts. Looking up the configuration inheritance chain, the Write ACLs on the various configuration files are as follows: ❑❑ Only the local Administrators group and SYSTEM have write access to files (including web.config files) located beneath inetpub\wwwroot. ❑❑ The root web.config file located at %windir%\Microsoft.NET\Framework\v2.0.XYZ\ CONFIG\web.config grants Write access only to the local Administrators group as well as SYSTEM. ❑❑ The machine.config located in the same CONFIG subdirectory also grants Write access only to the local Administrators group as well as SYSTEM. This set of ACLs shows that the default privileges pretty much expect only interactive editing of configuration files by a machine administrator using Notepad. Write access alone, however, is not sufficient for editing configuration files using the configuration API. Updating configuration information results in the following file operations: 1. A temporary file is created in the appropriate directory where the updated configuration file will be written. For example, if you are updating a configuration section in a web application’s configuration file, the configuration system will create a temporary file with a random file name in the web application’s root directory. 2. The original configuration file is deleted. 3. The temporary file is renamed to either web.config or machine.config, depending on which type of configuration file is being edited. From this list it is pretty obvious that editing and updating configuration files requires very powerful privileges. Download at Boykma.Com 249 Chapter 5: Configuration System Security Because of the creation and deletion of configuration files, the operating system thread identity that is updating configuration effectively requires Full Control to the directory containing the configuration file that will ultimately be rewritten (technically, you can get away with just Write and Modify access on the directory, but realistically there isn’t much difference between Full Control and Write+Modify). Although you could go out of your way and attempt to grant Full Control on a directory but restrict the rights on all files except the configuration file located within a directory, such a security lockdown doesn’t buy you much. Full Control on a directory gives an account wide latitude to make changes in it, and arguably the ability to change the configuration file means an account also has broad privileges to change the behavior of an application. An important side note here is that because local administrators do have Full Control to directories, a website with Windows authentication and client impersonation enabled could “accidentally” write to any of these configuration files. If a user account that was a member of the local Administrators group happened to surf to a web application that included malicious code that attempted to rewrite configuration, the malicious code would succeed. This type of subtle attack vector is another reason users with elevated privileges in a domain should never perform routine day-to-day work logged in with “super” privileges; its far too easy for someone to slip a piece of interesting code into an unsuspecting web application that maliciously makes use of such elevated privileges. Unlike the Read-oriented methods in configuration that are split between a set of runtime and designtime APIs, Write operations are considered design-time APIs. There is no equivalent to GetSection for writing configuration. In fact, if you obtain a configuration section via GetSection, although you can call the property setters on the strongly typed configuration section that is returned, no methods are available to commit the changes to the underlying configuration file. Instead, you commit changes to disk with a call to the Save or SaveAs method available on System​ .Configuration.Configuration. The Configuration instance can be obtained via a call to one of the Open* methods available on ConfigurationManager or WebConfigurationManager. Remember that the operating system thread identity requires Read access to successfully load a configuration file (or files) from disk; loading these files is always the first step whenever you want to edit configuration. After a call to WebConfigurationManager.OpenWebConfiguration, you have a Configuration object that is a reference to an in-memory representation of the loaded configuration file. Subsequently calling Configuration.Save or Configuration.SaveAs results in the file creation and deletion operations listed earlier. The following code snippet loads a web application’s configuration, modifies the configuration information in memory, and then writes the results to disk: C# Configuration config = WebConfigurationManager.OpenWebConfiguration(“~”); MembershipSection ms = (MembershipSection)config.GetSection(“system.web/membership”); ms.DefaultProvider = “someOtherProvider”; config.Save(); 250 Download at Boykma.Com Chapter 5: Configuration System Security VB.NET Dim config As Configuration = WebConfigurationManager.OpenWebConfiguration(“~”) Dim ms As MembershipSection = _ CType(config.GetSection(“system.web/membership”), _ MembershipSection) ms.DefaultProvider = “someOtherProvider” config.Save() In the sample code, the configuration information being edited is the web.config file for a web application; thus, Full Control is required only on the root of the web application’s directory. The configuration information represented by the Configuration instance is loaded by reading all the configuration files up the configuration inheritance chain. In an application using Windows authentication and client impersonation, the resulting operating system thread identity needs Read access on each of these configuration files. However, because the web application’s configuration was loaded (as opposed to the root web.config or the machine.config), Full Control is needed only on the web application’s root directory when the call to Save is made. The requirements for Full Control raise the question of exactly when it makes sense to use the designtime APIs. The safest approach would be to never deploy code to a production web server that calls Configuration.Save. The design-time aspect of configuration makes a lot of sense to use in a development environment or in an automated build process. However, after you have programmatically generated the desired configuration file, you would copy it to a production server. If the need to edit the configuration files used in production arises, it still makes sense to have the code that performs the configuration updates run on some type of staging or test server. After you verify that the updated configuration works, the updated configuration file can be staged and copied to production. I think having code that writes to configuration sitting on a production server, along with a set of file permissions granting Full Control, is simply a hacker attack waiting to happen. There is no escaping the fact that you need Full Control to save configuration changes to disk. The idea of having Full Control ACLs for anything other than local administrators placed on the directories of various application folders is pretty scary. Although there will surely be many elegant and powerful configuration-editing UIs created for ASP.NET 3.5 (IIS 7.0 allows editing web server configuration settings remotely using IIS 7.0 Manager), such tools should be tightly controlled. Setting up a website or a Web Service that allows for remote editing of configuration files on a production server is just a security incident waiting to happen. Permissions Required for Remote Editing The configuration system for ASP.NET includes the ability to have code on one machine remotely bind to ASP.NET configuration data on a remote server and read or write that configuration information. For security reasons, this capability is not enabled by default. A DCOM object can be enabled on your web server to allow remote machines to connect to the web server and carry out configuration operations. Download at Boykma.Com 251 Chapter 5: Configuration System Security To enable remote reading and writing of a web server’s configuration information, you use the aspnet_regiis tool: %windir%\Microsoft.NET\Framework\v2.0.5727\aspnet_regiis –config+ The config+ switch causes the Framework to register a DCOM endpoint with the following PROGID: System.Web.Configuration.RemoteWebConfigurationHostServer_32 If you use the DCOMCNFG tool (which is now an MMC console showing both COM+ and standard DCOM information) after running aspnet_regiis –config+, you can open the DCOM configuration node to see the newly registered DCOM endpoint, as shown in Figure 5-4. Figure 5-4 You can subsequently disable remote editing of configuration by using aspnet_regiis -config-. You run the aspnet_regiis tool on the web servers that you want to manage. However, it isn’t necessary to run the tool on the machine that will be running the configuration code. Within the web configuration code, whenever you attempt to open configuration information on a remote server, the configuration code attempts to create an instance of the DCOM object on the remote server. This requires that DCOM calls are able to flow across the network between the machine running the configuration editing code, and the remote server. Due the sensitive nature of allowing code to remotely manipulate a server’s configuration information, the DCOM object on the remote web server has its launch permissions restricted to only members of the remote server’s local Administrators group. Remember that this is the same security requirement needed by default for editing local configuration information. This means that even if you call one of the Open* methods with the intent of only reading configuration information from a remote server, 252 Download at Boykma.Com Chapter 5: Configuration System Security the operating system thread identity making the calls still needs to be a member of the remote server’s Administrators group. The more stringent security requirement is necessary because you don’t want random machines on your network trolling through your servers attempting to remotely read configuration information. The utility of allowing remote editing of configuration is suspect due to the security risks involved. With the additional requirement of configuring DCOM to work through firewalls if you are attempting to manage web servers in a DMZ, remote configuration editing in ASP.NET is most useful for web servers running inside of a corporate network. Even then you should use additional security such as IPSEC restrictions to prevent random machines on your network from attempting to launch the DCOM server on your web machines. For additional security, you should change the access permissions on the DCOM object. Although the launch permissions are locked to the local Administrators group, after the DCOM server is launched the default DCOM access permissions control which identities can invoke methods on the DCOM server. Creating a custom set of access permissions for the configuration DCOM object ensures that only selected users or groups can invoke methods on the DCOM server after it is already started. Using Configuration in Partial Trust The configuration examples you have seen so far all depended implicitly on one additional security setting in order to work: the trust level for the sample application. The sample applications have all been running in Full trust when calling into the configuration system. If you attempt to use the strongly typed configuration API, you can only do so by default when running in either Full or High trust. At lower trust levels, the strongly typed configuration API will fail. For example, say you attempt to read the Membership configuration with code like the following: C# MembershipSection ms = (MembershipSection)ConfigurationManager.GetSection(“system.web/membership”); VB.NET Dim ms As MembershipSection = _ CType(config.GetSection(“system.web/membership”), _ MembershipSection) If your application is running in Medium trust or below, you get an exception with the following information: Request for the permission of type ‘System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089’ failed. (machine.config) Stack Trace: … at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) Download at Boykma.Com 253 Chapter 5: Configuration System Security Chapter 4 explained that when you encounter permission-related exceptions, the exception information and stack trace can sometimes give you a clue as to what happened. In this case, it looks like the configuration system made a check for a permission, specifically the System.Configuration.Configuration​ Permission. The configuration system always demands the ConfigurationPermission whenever an attempt is made to retrieve a configuration object with a call to GetSection. If you look in the policy file for High trust, you can see that the ConfigurationPermission is explicitly granted: The High trust policy file defines the necessary security class for ConfigurationPermission and then grants unrestricted permission on ConfigurationPermission to any ASP.NET application running in High trust. When running at Full trust (the default for all ASP.NET applications), the demand for ConfigurationPermission always succeeds. If you look in the trust policy files for Medium, Low, and Minimal trust, you will see that these policy files do not define a for ConfigurationPermission and thus do not grant this permission in the ASP.NET NamedPermissionSet. With this behavior, you might be wondering how any of the ASP.NET 3.5 features that depend on configuration even work in lower trust levels. For example, the Membership feature clearly depends heavily on a variety of configuration information. You can definitely use the Membership feature in Medium trust without any SecurityExceptions being thrown, so what is going on to make this work? ASP.NET 3.5 features that retrieve their configuration sections use an internal helper class that asserts unrestricted ConfigurationPermission. Because the core of ASP.NET 3.5 lives in the GAC’d System.Web.dll assembly, the assertion is allowed. At runtime when various ASP.NET features retrieve their configuration information, the ConfigurationPermission demand from the configuration system succeeds when the demand encounters the assertion during the stack crawl. The combination of the configuration system’s demand and the assertion within ASP.NET is why in many places in this book I note that strongly typed configuration information is not something that can be depended on when running in partial trust (Medium trust or lower to be specific). This is also why most of the ASP.NET features mirror their configuration information through some portion of their API. 254 Download at Boykma.Com Chapter 5: Configuration System Security For example almost all of the configuration attributes found on the configuration element and its provider elements can be found on Read-Only properties, either Read-Only properties on the static Membership class or exposed as Read-Only properties from MembershipProvider. The design approach of echoing back configuration properties on a feature class is one you should keep in mind when designing configuration-driven features. If you design a feature intending that aspects of its configuration be available to developers, then you can do the following: 1. Author the feature to live in the GAC. Follow the design guidelines in Chapter 4 for writing a sandboxed GAC-resident assembly. 2. Within your feature code, assert the ConfigurationPermission when your feature reads its configuration information. 3. Create one or more Read-Only properties on your feature classes that echo back the appropriate portions of your configuration information. Of course, there is one flaw with this approach: You may not be allowed to deploy your feature into the GAC. Especially if you write code for use by customers running on shared hosting servers, it is likely that your customers will be unable to deploy your feature’s assembly into the GAC. There is a workaround for this scenario, though. The requirePermission Attribute The
configuration element introduced in 2.0 Framework a new attribute, require Permission. By default, this attribute is set to true, which triggers the configuration system to demand the ConfigurationPermission. However, if you set it to false, the configuration system bypasses the permission demand. For example if you tweak the definition of the configuration section to look like the following:
… the sample shown earlier using GetSection will work when running Medium trust or below. However, even though you can add the requirePermission attribute, it is not a recommended approach for the built-in ASP.NET features. The ConfigurationPermission is intended to close the following loophole. Because the configuration system is fully trusted (it lives in the various GAC’d assemblies), and the configuration system is usually invoked initially without any user code on the stack, the configuration system ends up loading configuration data that is potentially sensitive. The theory is that the configuration data should be treated in such a way that only fully trusted code is allowed Read and Write access to it. If the configuration system allowed partially trusted code (that is, partial trust ASP.NET pages) to read and write configuration data, then the configuration system theoretically opens itself to a luring attack. Partially trusted code would be able to gain access to some configuration data that it normally would not be able to read. Of course, one quirk with this theory is that even in Medium and Low trust you can write code in your pages that opens up the application’s web.config as a raw text file, at which point you can parse through it and find the configuration information. However, configuration information is hierarchical, so Download at Boykma.Com 255 Chapter 5: Configuration System Security it is likely that some of your application’s configuration information lives in the parent configuration files. Using simple file I/O, you will not be able to discover the settings stored in either the root web.config or in machine.config when running in Medium trust or below. The use of the ConfigurationPermission is a code access security (CAS)-based approach to ensuring that partial trust code cannot use the configuration system to gain access to these parent configuration files when a simple file I/O based approach would fail. The ConfiguartionPermission is granted to High trust because High trust applications also have the necessary FileIOPermission to read the root web.config and machine.config files. So, the default High trust policy file ensures that the configuration system and the permissions for performing raw file I/O are in sync. Of course, as with all security policies defined using trust policy files, you can create a trust policy file that breaks this; you could, for example, grant ConfigurationPermission in the Medium trust policy file, although this is not something you should do. So, when should you use the requirePermission attribute to override the default demand for ConfigurationPermission? If you author a configuration-driven feature that will not live in the GAC, it makes sense to include the requirePermission attribute in the
definition for your custom configuration section. A feature that does not live in the GAC is basically a partially trusted feature itself; conceptually, it would not be considered any more sensitive than the partially trusted code that calls it. Hence, it is reasonable to allow partially trusted code access to the strongly typed configuration class for such a feature. Of course, if partially trusted code attempts to write changes for the feature back to the underlying configuration files, it still needs the appropriate FileIOPermission and the appropriate NTFS permissions. With these additional security requirements required for updating configuration, setting the requirePermission attribute in your custom configuration sections for nonGAC’d features doesn’t open any security holes. The behavior of the requirePermission attribute suggests that you should ensure that all GAC’d features have
definitions in machine.config or web.config because after a
is defined in a configuration file, child configuration files cannot override the definition. Even if a child configuration file like an application web.config attempts to add the requirePermission=’false’ attribute, the configuration system disallows this redefinition of the configuration section. When setting up the configuration section for a feature, you should do one of the following: ❑❑ For GAC, based features, define
in machine.config or the root web.config file. ❑❑ For non-GAC’d features running in shared hosting environments, define the
in the application’s web.config file, and set requirePermission to false. This also means that you will only be able to include the feature’s configuration section in the application’s web.config file. If you place the feature’s configuration in a higher level configuration file you get an exception because the
has not been defined yet. ❑❑ For non-GAC’d features running in some type of trusted environment (such as an internal corporate web server), you can define the
wherever it makes sense for manageability. You may define your
in machine.config or root web.config to allow multiple web applications to take advantage of the feature. This is one case where it is reasonable for a non-GAC’d feature to have its
definition in a parent configuration file while still setting requirePermission to false. There are two configurations sections defined in machine.config that set requirePermission to false: and . Because these configuration sections are typically used directly by application code, locking them down for partial trust applications does not 256 Download at Boykma.Com Chapter 5: Configuration System Security make sense. As a result, these two configuration sections are the exception to the rule that GAC’d configuration sections disallow strongly typed configuration access to partial trust applications. Demanding Permissions from a Configuration Class There is little-known capability in the configuration system that you can use for supporting partial trust applications. You can use a custom configuration class as a kind of gatekeeper to a feature and prevent the feature from being used in a partial trust application. If you remember back to the Chapter 4 on trust levels, and the discussion on the “processRequestInApplicationTrust” attribute, there is a subtle issue with features and code being called when only trusted code is on the stack. Custom configuration classes are part of this issue because when configuration is being loaded, it is not guaranteed that there will be any user code on the stack. More important, the feature that carries out work and that consumes the configuration information may itself always be called with trusted code on the stack. Scenarios like GAC’d classes that are HttpModules have this problem. An HttpModule only has the ASP.NET pipeline code sitting above it, so any demands a custom HttpModule located in the GAC makes always succeed. A feature can indirectly work around this problem by taking advantage of the fact that the configuration system calls PermitOnly on the named permission set for the current trust level. This behavior is the same approach that the page handler takes when it calls PermitOnly prior to running a page. The configuration system makes this call just before attempting to deserialize a configuration section. As a result, a custom configuration class that overrides ConfigurationSection.PostDeserialize can demand an appropriate permission in an override of this method. C# using System; using System.Data.SqlClient; using System.Security.Permissions; using System.Configuration; public class SkeletalConfigClass: ConfigurationSection { public SkeletalConfigClass() {} protected override void PostDeserialize() { SqlClientPermission scp = new SqlClientPermission(PermissionState.Unrestricted); scp.Demand(); } //the rest of the configuration class… } VB.NET Imports System Imports System.Configuration Imports System.Security.Permissions Imports System.Data.SqlClient Download at Boykma.Com 257 Chapter 5: Configuration System Security Public Class SkeletalConfigClass Inherits ConfigurationSection Public Sub New() End Sub Protected Overrides Sub PostDeserialize() Dim scp As New SqlClientPermission(PermissionState.Unrestricted) scp.Demand() End Sub ‘the rest of the configuration class… End Class The previous configuration class demands the SqlClientPermission. Because the configuration system restricts the set of allowed permissions to whatever is defined for the application’s current trust level prior to the deserialization process, the sample configuration class is usable only if the current trust level grants the SqlClientPermission. If a feature living in the GAC attempts to read its configuration information and the current trust level doesn’t grant this permission, the feature initialization fails because any attempt to read its configuration always fails with a SecurityException. Given this capability, when would you actually use it? Should you always demand something from your custom configuration class? If you know your GAC’d code is going to be called in scenarios where only trusted code exists on the stack, you should make use of the PostDeserialize method. It is the only point when you will have a chance to enforce a CAS restriction. Identifying these scenarios can be difficult, though. If your feature includes a GAC’d HttpModule, this is one obvious case. A custom handler that is deployed in the GAC would be another example where using PostDeserialize as a surrogate trust enforcement mechanism makes sense. However, it may impossible to make an intelligent demand in PostDeserialize if you depend on the code that consumes your feature to supply dynamic information. For example, if your feature reads and writes to the file system, you may not know which path to demand permission against until after some consumer code sets some properties on your feature. As a result, the PostDeserialize method is appropriate only for demanding permissions that always need to be statically configured in a trust policy file. FileIOPermission and the Design-Time API Unlike the runtime portion of the configuration API (for example, GetSection), the design-time API always results in physical file I/O operations occurring up the chain of parent configuration files. Because in Medium trust an ASP.NET application only has rights to read and write files within the application’s directory structure, partial trust code doesn’t have rights to open files outside the application. For this reason, the design-time API is basically useless when running in Medium trust or below. Although you could theoretically tweak the lower trust levels’ policy files to get the design-time API working, it is better to consider the design-time API suitable only for Full trust or High trust applications. If you attempt to use one of the design-time APIs such as WebConfigurationManager.OpenWeb Configuration in partial trust, you will run into an exception like the following: SecurityException: Request for the permission of type ‘System.Security.Permissions. FileIOPermission, …’ failed.] …snip… System.Security.CodeAccessPermission.Demand() 258 Download at Boykma.Com Chapter 5: Configuration System Security System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy) System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share) …snip… System.Configuration.UpdateConfigHost.OpenStreamForRead(String streamName) System.Configuration.BaseConfigurationRecord.InitConfigFromFile() This stack trace shows that the open attempt eventually results in the use of the FileStream object. Attempting to open a FileStream on top of a file always results in a demand for a FileIOPermission. So, long before the configuration system ever gets around to demanding ConfigurationPermission, the file I/O that occurs during a call to OpenWebConfiguration in a partial trust application will fail. This behavior is another reason the design-time APIs are useful only in High and Full trust web applications. Protected Configuration Since ASP.NET 1.0, a common request has been for a way to safely store sensitive configuration information and shield it from prying eyes. The most common information that developers want to protect is connection strings because these frequently contain username-password pairs. But sorts of interesting information beyond connection strings is contained within ASP.NET configuration files. If you use the section, you again have credentials stored in configuration. If you use classes in the System.Net namespace, you may have configuration elements listing out SMTP servers or other network endpoints and so on. Since the 2.0 Framework, there has been a feature to deal with this problem called protected configuration. Protected configuration is a way to take selected pieces of any configuration file and store the configuration information instead in a secure and encrypted format. The great thing about the protected configuration feature is that it can be used with just about any configuration section—both ASP.NET and non-ASP.NET configuration sections. As with other features in ASP.NET, protected configuration is provider-based, so you can buy or write alternative protected configuration providers instead of using the built-in providers. Out of the box, the .NET Framework ships with two protected configuration providers: ❑❑ System.Configuration.DPAPIProtectedConfigurationProvider ❑❑ System.Configuration.RsaProtectedConfigurationProvider As the class names suggest, the first provider uses the data protection API (DPAPI) functionality in Windows to encrypt and decrypt configuration sections. The second provider uses the public-key RSA algorithm for performing the same functionality. The basic idea behind protected configuration is that you use the aspnet_regiis command-line tool, or the configuration API (the SectionInformation.ProtectSection and SectionInformation​ .UnprotectSection methods, to be precise) to encrypt selected pieces of your configuration informa- tion prior to putting an application into production. Then at runtime the configuration system decrypts the protected configuration information just prior to handing the configuration information back to the requesting code. The important thing is that protecting a configuration section is transparent to the fea- tures that rely on the configuration section. No feature code has to change just because an underlying configuration section has been encrypted. Download at Boykma.Com 259 Chapter 5: Configuration System Security When you use protected configuration you start with configuration section that might look like the following: This is a perfect example of the type of section you probably would like to protect. You would rather not have any random person with Read access to your web.config walking away with the signing and validation keys for your application. You can encrypt this configuration section from the command line using the aspnet_regiis tool: aspnet_regiis -pe system.web/machineKey -app / ConfigurationSample -prov DataProtectionConfigurationProvider After you use the protected configuration feature, the section looks something like the following: encrypted data here Of course, instead of the text “encrypted data here,” the actual result has about five lines of text containing the base-64 encoded representation of the encrypted blob for the section. When you run the application, everything still works normally, though, because internally the configuration system transparently decrypts the section using the extra information added to the element. Depending on whether you use the RSA- or the DPAPI-based provider, different information will show up within the element. In the previous example, the configuration, system added the configProtectionProvider attribute to the element. This is a pointer to one of the protected configuration providers defined in machine.config. At runtime, the configuration system instantiates the specified provider and asks it to decrypt the contents of the element. This means that custom protected configuration providers can place additional information within the element containing any extra information required by the provider to successfully decrypt the section. In the case of the DPAPI provider, no additional information behind the encrypted blob is necessary. What Can’t You Protect? Protected configuration sounds like the final answer to the age-old problem of encrypting connection strings. However, due to the interaction between app-domain startup and configuration, you cannot blindly encrypt every single configuration section in your configuration files. In some cases, you have a “chicken-and-egg” effect where ASP.NET or the Framework needs to read configuration information to bootstrap itself, but it has to do this prior to having read the configuration information that defines the protected configuration providers. 260 Download at Boykma.Com Chapter 5: Configuration System Security The following list names some configuration sections (this is not an exhaustive list) that you may have in your various configuration files that can’t be encrypted with protected configuration: ❑❑ processModel: ASP.NET needs to be able to read this just as it is starting up. Furthermore, for IIS 5 and IIS 5.1 it controls the identity of the worker process, so you would be in a Catch22 situation if you needed the correct worker process identity in order to read protected configuration. ❑❑ startup and runtime: These configuration sections are used by the Framework to determine things such as which version of the Framework to load as well as information on assembly redirection. ❑❑ cryptographySettings: This configuration section defines the actual cryptography classes used by the framework. Because protected configuration depends on some of these classes, you can’t encrypt the configuration section that contains information about the algorithms used by the protected configuration feature. ❑❑ configProtectedData: This is the configuration section that contains the definition of the protected configuration providers on the machine. This would also be a Catch-22 if the section were encrypted because the configuration system needs to be able to read this section to get the appropriate provider for decrypting other configuration sections. Selecting a Protected Configuration Provider Now that you know you have at least two different options for encrypting configuration information, you need to make a decision about which one to use. Additionally, you need to determine how you want to use each provider. The criteria for selecting and then configuring a provider revolve around two questions: ❑❑ Do you need to share configuration files across machines? ❑❑ Do you need to isolate encrypted configuration data between applications? The first question is relevant for those of you that need to deploy an application across multiple machines in a web farm. Obviously in a load-balanced web farm, you want an application that is deployed on multiple machines to use the same set of configuration data. You can use either the DPAPI provider or the RSA provider for this scenario. Both providers require some degree of setup to work properly in a web farm. Of the two providers, the RSA provider is definitely the more natural fit. With the DPAPI provider, you would need to do the following to deploy a web.config file across multiple machines: 1. Deploy the unencrypted configuration file to each web server. 2. On each web server, run aspnet_regiis to encrypt the desired configuration sections. The reason for this is that the DPAPI provider relies on machine-specific information, and this information it not portable across machines. Although you can make the DPAPI provider work in a web farm, you will probably get tired of constantly re-encrypting configuration sections each time you push a new configuration file to a web farm. Download at Boykma.Com 261 Chapter 5: Configuration System Security The RSA provider depends on key containers that contain the actual key material for encrypting and decrypting configuration sections. For a web farm, you would perform a one-time setup to synchronize a key container across all the machines in a web farm. After you create a common key container across all machines in the farm, you can encrypt a configuration file once on one of the machines, perhaps even using a utility machine that is not part of the web farm itself but that still has the common key container. When you push the encrypted configuration file to all machines in the web farm, each web server is able to decrypt the protected configuration information because each machine has access to a common set of keys. The second question around isolation of encryption information deals with how the encryption keys are protected from other web applications. Both the DPAPI and the RSA providers can use keys that are accessible machine-wide, or use keys that are accessible to only a specific user identity. RSA has the additional functionality of using machine-wide keys that only grant access to specific user accounts. Currently, the recommendation is that if you want to isolate key material by user account, you should separate your web applications into different application pools in IIS 7.0, and you should use the RSA provider. This allows you to specify a different user account for each worker process. Then when you configure the RSA-protected configuration providers, you take some extra steps to ensure that encryption succeeds only while running as a specific user account. At runtime, this means that even if one application can somehow gain access to another application’s configuration data, the application will not be able to decrypt it because the required key material is associated with a different identity. Both the DPAPI and RSA have per-user modes of operation that can store encryption material directly associated with a specific user account. However, both of these technologies have the limitation that the Windows user profile for the process identity needs to be loaded into memory before it can access the necessary keys. Loading of the Windows user profile does not happen on IIS 6 and 7.0 (it will occur, though, for other reasons in IIS 5/5.1). As a result, the per-user modes for the DPAPI and RSA providers really aren’t useful for web applications. There is another aspect to isolating encryption data for the DPAPI provider because the provider supports specifying an optional entropy value to use during encryption and decryption. The entropy value is essentially like a second piece of key material. Two different applications using different entropy values with DPAPI will be unable to read each other’s data. However, using entropy is probably more suitable when you want the convenience of using the machine-wide store in DPAPI, but you still want some isolation between applications. The following table summarizes the provider options that you should consider before setting up protected configuration for use in ASP.NET: Sharing key material is acceptable Need to Support Multiple Machines RSA provider. Use the default machinewide key container, and grant Read access to all accounts. Only Deploy on a Single Machine Either the RSA or the DPAPI provider will work. Use the machine-wide options for either provider. Can optionally use key entropy with DPAPI provider. Can optionally use RSA key containers with different ACLs. 262 Download at Boykma.Com Chapter 5: Configuration System Security Key material should be isolated Need to Support Multiple Machines RSA provider. Use machine-wide RSA key containers, but ACL different key containers to different user identities. Only Deploy on a Single Machine RSA provider. Use machine-wide RSA key containers, but ACL different key containers to different identities. DPAPI per-user key containers require a loaded user profile and thus should not be used. RSA per-user key containers also require a loaded user profile and thus should not be used. Caveat When Using Stores That Depend on User Identity If you choose to use either provider with their per-user mode of operation or if you use machine-wide RSA key containers that are ACL’d to specific users, you need to be aware of an issue with using protected configuration. The sequence in which ASP.NET reads and then deserializes configuration sections is not fixed. Although ASP.NET internally obtains configuration sections in a certain sequence during app-domain startup, this sequence may very well change in the future. One very important configuration section that is read early on during app-domain startup is the section. You can use to configure application impersonation for ASP.NET. However, if you use RSA key containers, for example, that depend on specific user identities, you can end up in a situation where ASP.NET starts initially running as a specific process identity (NETWORK SERVICE by default on IIS 6 and 7.0), and then after reading the section it switches to running as the defined application impersonation identity. This can lead to a situation where you have granted permission on an RSA key container to an IIS 6 or 7.0 worker process account, and suddenly other configuration sections are no longer decrypting properly because they are being decrypted after ASP.NET switches over to the application impersonation account. As a result, you should always configure and ACL key stores on the basis of a known process identity. For IIS 6 or 7.0 this means setting up protected configuration based on the identity that will be used for an individual worker process. If your applications need to run as different identities, instead of using application impersonation on IIS 7.0, you should separate the applications into different application pools (aka worker processes). This guarantees that at runtime ASP.NET will always be running with a stable identity, and thus regardless of the order in which ASP.NET reads configuration sections during app-domain startup, protected configuration sections will always be capable of being decrypted using the same identity. For older versions like IIS 5 and IIS 5.1, you can choose a different process identity using the element. However, application impersonation is really the only way to isolate applications by identity on these older versions of IIS. Although you could play around with different configuration sections to determine which ones are being read with the identity defined in and which ones are Download at Boykma.Com Continued 263 Chapter 5: Configuration System Security read using the application impersonation identity in , you could very well end up with a future service pack subtly changing the order in which configuration sections are deserialized. As a result, the recommendation for IIS 5/5.1 is to upgrade to IIS 6 or IIS 7.0 if you want to use a feature like RSA key containers with user-specific ACLs. Granted that this may sound a bit arbitrary, but using key storage that depends on specific identities with protected configuration gets somewhat complicated as you will see in a bit. Attempting to keep track of the order of configuration section deserialization adds to this complexity and if depended on would result in a rather brittle approach to securing configuration sections. Separating applications with IIS 6 or 7.0 worker processes is simply a much cleaner and more maintainable approach over the long term. Defining Protected Configuration Providers The default protected configuration providers are defined in machine.config: If you author or purchase a custom provider, you would configure it in the section and assign it a name so that tools like aspnet_regiis can make use of it. Other than the “name” and “type” attributes, all of the information you see on the provider elements is unique to each specific provider. Custom providers can support their own set of configuration properties that you can then define when you configure them with the element. As with most other provider-based features, you can define as many protected configuration providers as you want. Then when using a tool like aspnet_regiis, writing code with the ProtectSetion method, or creating web.config files, you can reference one of the protected configuration providers from by name. For example, the -prov command-line switch you saw earlier on aspnet_regiis refers to a named provider within . In these scenarios, if you do not explicitly select a provider, then the value of defaultProvider on the element is used. This means that by default the RSA provider is used for protected configuration. 264 Download at Boykma.Com Chapter 5: Configuration System Security DpapiProtectedConfigurationProvider This protected configuration provider uses the data protection API (DPAPI) that is part of Windows. This functionality will probably be familiar to those of you who used the aspnet_setreg tool back in ASP.NET 1.1 or who wrote a managed DPAPI wrapper for use in applications. The nice thing about the DPAPI provider is that it is very easy to use. Configuring the provider is quite simple because you need to consider only two provider-specific options: ❑❑ keyEntropy: This is a string value containing some random information that will be used during the encryption process. If you use a different keyEntropy value for each application, applications that share the same set of DPAPI encryption keys still cannot read each other’s protected configuration data. ❑❑ useMachineProtection: Because DPAPI has the concept of a machine store and a per-user store, this configuration attribute indicates which one to use. If you set this attribute to true (the default), all applications can decrypt each other’s protected configuration data. If you set this attribute to false, then only applications running under the same credentials will be able to decrypt each other’s protected configuration data. The DPAPI provider should really be used only for single-machine applications. Although you can go through a manual step whereby you always re-encrypt your configuration files after they have been deployed to a machine, this is inconvenient. Furthermore, it opens up the possibility of someone forgetting to encrypt a configuration file (and remember that you may need to encrypt multiple configuration files up the configuration inheritance hierarchy). keyEntropy The keyEntropy option is only useful for giving a modicum of protection against two different applications reading each other’s configuration data when useMachineProtection is set to true. With the machine-wide DPAPI key store, technically anyone who can get code onto the machine will be able to decrypt your protected configuration data. Specifying an entropy value gives you a lightweight approach to protecting the encrypted data. You can use keyEntropy with the per-user mode of operation for DPAPI as an additional layer of protection, although the per-user mode for the DPAPI provider is not suitable for use with web applications. If each web application uses a different keyEntropy parameter in its configuration, only code with knowledge of that value will be able to read the configuration data. Of course, the management problem with using keyEntropy is that you need a separate provider definition for each different keyEntropy value. If you have a fair number of applications to protect on a server, and you want to isolate the encrypted data between each application, you can easily end up with dozens of provider definitions just so that you can use a different keyEntropy value for each application. There is also the related issue that you need to ACL the appropriate configuration files so that random users cannot open them and read the configuration. Placing the different provider definitions in machine.config or the root web.config prevents applications running at Medium trust or lower from being able to use the strongly typed configuration classes to read the raw provider definitions (note that the actual provider class DpapiProtectedConfigurationProvider doesn’t expose the keyEntropy value as a property). Download at Boykma.Com 265 Chapter 5: Configuration System Security However High and Full trust applications have the ability to open any file on the file system (ACLs permitting). For these types of applications, you need to run each application in a separate application pool with each application pool being assigned a different user identity. With this approach, you can then place each application’s provider definition within the application’s web.config file, and the ACLs prevent one worker process from reading the configuration file from another application. If you were to leave the application-specific provider definition in machine.config or web.config, Full and High trust applications would be able to open these files and read the keyEntropy attribute. Using keyEntropy is pretty basic: You just define another instance of the DPAPI provider and put any value you want as a value for this attribute: You should set the keyEntropy value to something that cannot be easily guessed. In this case, I just used a random string of characters. Any long string of random values will work; there are no restrictions on the length of the keyEntropy configuration attribute. If another application attempts to decrypt a protected configuration section and uses a different entropy value, it receives an error message stating that the data in the configuration section is invalid. useMachineProtection The default DPAPI configuration uses the machine-wide DPAPI key store; if you configure the DPAPI provider and fail to set the useMachineProtection attribute, internally the provider will also default to using the machine-wide store. If you are running in a trusted environment and it doesn’t really matter if applications can read each other’s configuration data, this setting is reasonable. However, if you are on a machine that hosts applications from development groups that don’t trust each other, or if you have a business requirement that different applications should not be able to read each other’s configuration data, setting useMachineProtection to false is an option. If you set this attribute to false, the identity of the application needs to be switched to a different user account (see the earlier section on using per-user key stores). Of course, after you change your application to run as a different identity, you already have the option of using file ACLs as a protection mechanism for preventing other applications from reading your configuration data. In a sense, using the per-user mode of the DPAPI provider is an additional layer of protection above and beyond what you gain just by changing applications to run as different user identities. As mentioned earlier, though, there is a pretty severe limitation if you set useMachineProtection to false. Due to the way DPAPI works, it needs access to the user profile for the process identity to access the key material. On IIS 7 the user profile for a worker process account (specifically machine or domain accounts other than LOCAL SERVICE or NETWORK SERVICE) is never loaded by IIS. If you follow the steps outlined in this section everything will work until you reboot the machine and the side effects of the runas command window are lost. If you really, really want to get per-user DPAPI working, you need a hack such as launching runas from a scheduled task or having an NT service that forcibly loads the profile for a user identity. Realistically, though, I would never depend on such workarounds for a 266 Download at Boykma.Com Chapter 5: Configuration System Security production application, and hence the machine store for the DPAPI protected configuration provider is the only really viable option for web applications. Non-ASP.NET applications do not have the limitation with the Windows user profile, though, so you may be interested in using DPAPI user stores for securing configuration information used by a fat client application. To set up the provider for per-user DPAPI, just change the useMachineProtection attribute to false: If you use DPAPI with per-user keys you must run interactive tools like aspnet_regiis with the process credentials that will be used at runtime. The simplest way to do this is with the runas command to spawn a separate command window. Of course, this also implies that you should choose a local or domain user account for your process identity because you aren’t going to know the password for the built-in NETWORK SERVICE account. After you spawn a command window running as the proper credentials, you can use the aspnet_ regiis command to encrypt the desired configuration section. Because encrypting a configuration file requires writing a temporary file, replacing the original configuration file, and then cleaning up afterward, the identity you are running as will temporarily need Read, Write, and Modify access to the application’s directory. After the encryption operation is done, you can remove the Write and Modify privileges from the directory. After the configuration file has been encrypted, try moving the web application into an IIS 7 application pool running with the same credentials that were used to run aspnet_regiis in the spawned command window. Now when you run your web application, the encrypted sections will be transparently decrypted using the DPAPI key associated with the worker process identity. If you assign your application to a different application pool, for example the default application pool running as NETWORK SERVICE, you will see the effect of the per-user DPAPI key. Running as NETWORK SERVICE instead returns an error message that the key is not valid for the specified state, meaning that you are attempting to decrypt the data with an invalid key. However, if you reboot your machine after the previous steps, your web application will stop working, even with everything set up properly, due to the dependence DPAPI has on the Windows user profile. As a result I would not recommend trying to get the per-user mode working for IIS 7. Also be aware that if you are running IIS 5 on a production machine, you can get the per-user mode of DPAPI to work because ASP.NET loads the user profile of the account specified in the element. However, if you move the application to an IIS 7 machine, it will fail because of the lack of a loaded Windows user profile for IIS 7. RsaProtectedConfigurationProvider As the name suggests this protected configuration provider uses the RSA public-key encryption algorithm for encrypting configuration sections. To be precise, the provider encrypts configuration sections using 3DES, but it then encrypts the symmetric 3DES key using the asymmetric RSA algorithm. Download at Boykma.Com 267 Chapter 5: Configuration System Security Of the two providers included in the Framework, this is definitely the preferred provider for a variety of reasons: ❑❑ It works well in multi-machine environments. ❑❑ It supports per-user key container ACLing without any awkward dependence on user profiles. ❑❑ As a result of its use of RSA, you can use other Windows cryptographic service providers for the RSA algorithm. Because the provider internally uses the RSA classes in the framework, it is able to support exporting and importing key material. This means there is a viable approach for synchronizing key material across multiple machines in a web farm. The concept of securing key containers to specific users does not depend on a Windows user profile; instead, it relies on having ACLs set up that grant access to specific user accounts that need to open and read key containers. As a result, using machine-wide containers with specific user ACLs is the preferred approach for isolating the encrypted configuration information for multiple applications. Because the provider uses RSA, and internally the Framework RSA classes rely on the Windows cryptographic API (CAPI), you get the added benefit of being able to use RSA key containers other than the default software-based Microsoft implementation. Although this last point is probably relevant for a small percentage of developers, if you happen to work in a bank or in the defense industry you are probably familiar with hardware cryptographic service providers (CSPs) for CAPI. If your organization uses Active Directory as a certificate store, you also may be using hardware-based CSPs. With the RsaProtectedConfigurationProvider, you have the option of configuring the protected configuration provider to use a custom CSP instead of the default software-based CSP. The configuration options of the RSA provider are a bit more extensive than those of the DAPI provider. Aside from the standard name, type, and description attributes, you can configure the following: ❑❑ useMachineContainer: As with the DPAPI provider you can use per-user key containers instead of machine-wide key containers. Like DPAPI, per-user key containers require a loaded Windows profile. Unlike DPAPI, machine-wide RSA key containers can be ACL’d to specific users. ❑❑ keyContainerName: The RSA provider always accesses keys from a software abstraction called a key container. From a manageability and security perspective, it makes it easier to separate different applications through the use of different key containers that are locked down to specific users. ❑❑ useOAEP: This option tells the providers to use Optional Asymmetric Encryption and Padding (OAEP) when encrypting and decrypting. Windows 2000 does not support this, so the default for this setting in configuration and inside of the provider is false. If you are running on Windows Server 2003, Server 2008, XP, or Vista, you can use this option because these operating systems support OAEP with RSA. ❑❑ cspProviderName: Assuming that you have registered a custom CSP for use with CAPI, you can tell the RSA configuration provider to use it by specifying the CSP’s name with this parameter. 268 Download at Boykma.Com Chapter 5: Configuration System Security Of the various parameters listed here, I will only drill into the useMachineContainer and key ContainerName attributes because these settings are the ones you will most commonly worry about. For IIS 7 on Windows Server 2008 or Windows Vista, you can optionally set useOAEP to true. For the cspProviderName attribute, if you already have a custom CSP configured on your web servers, you will already know the string name for using it with your applications. Beyond that, there is not anything else special that you need to do from the perspective of protected configuration. keyContainerName Regardless of whether you use a machine key container or a user-specific key container, the RSAprotected configuration provider needs to be pointed at the appropriate container. Unlike the DPAPI provider, the RSA provider does not have some central pool where keys are held. Instead, key material is always segmented into specific containers. The following default RSA provider configuration uses a default container name of NetFrameworkConfigurationKey: Encrypting a configuration section with aspnet_regiis using the RSA provider looks like the following: aspnet_regiis -pe system.web/machineKey -app /ConfigurationSample In this case, the -prov option was not used, meaning the default provider for protected configuration will be used, which is the RSA-based provider. Contrasted with the output from the DPAPI provider, the output from the RSA provider is substantially more verbose: Rsa Key encrypted 3DES key goes here encrypted machine key section here Download at Boykma.Com 269 Chapter 5: Configuration System Security The format for the RSA and DPAPI providers is based on the W3C XML Encryption Recommendation. However, the RSA provider output really needs the expressiveness of this format due to all of the information it needs to output. There are actually two separate elements. The first element contains an encrypted version of a 3DES key. The idea behind the RSA provider is that for each configuration section that is encrypted, the provider creates a new random symmetric key for 3DES. However, you don’t want to communicate that signing key in the clear. So, the symmetric key is encrypted using an asymmetric RSA public-private key pair. The end result of the asymmetric RSA encryption is placed within the first occurrence of the element. The only way that someone can actually decrypt the 3DES encryption key is to have the same public-private key pair in the appropriate RSA container on their system. The element that ends in rsa-1_5 tells the configuration system (or more precisely the XML Encryption support in the Framework) to use the RSA algorithm to decrypt the 3DES encryption key. Internally, the protected configuration provider will hand the Framework an instance of a System.Security.Cryptography.RSACryptoServiceProvider that has already been initialized with the appropriate RSA key container based on the configuration provider’s settings. The second element contains the actual results of encrypting the configuration section using 3DES. At runtime, the protected configuration provider will use the results of the RSA decryption for the 3DES key to in turn decrypt the second section into the cleartext version of a configuration section. Although a bit counterintuitive, if you rush out and use aspnet_regiis to encrypt a configuration section with the RSA provider, when you then run your ASP.NET application, it will fail with an error stating that the RSA key container cannot be opened. This is because although the Framework ensures that an RSA container called NetFrameworkConfigurationKey is created on the machine, by default the process account for your web application does not have rights to retrieve key material from the key container. You have to first grant read access on the key container using aspnet_regiis. For ASP.NET, you need to grant read access on the container to only the appropriate process account. Although aspnet_ regiis supports granting Full access to a key container, you don’t want the identity of a web application to have rights to write to or delete containers. As a result, for the default provider configuration, the process account for your web application needs only Read access. The following aspnet_regiis command grants Read access to the default RSA key container used by protected configuration: aspnet_regiis -pa “NetFrameworkConfigurationKey” “NT AUTHORITY\NETWORK SERVICE” After you do this, your web applications will be able to decrypt configuration sections using the default machine-wide container. Now that you understand the basics of using the default key container, the next question is when would you use alternate key containers? The combination of using machine-wide containers (for example, the useMachineContainer attribute is set to true) with key containers is compelling. You can log on to a web server as local machine administrator and create a machine-wide RSA key pair in a new container using the aspnet_regiis tool. You can then selectively grant Read access on the container to certain accounts. 270 Download at Boykma.Com Chapter 5: Configuration System Security This means you can segment your applications into different worker processes running with different user accounts, and grant each user account Read access to a specific key container. Unlike DPAPI, just because an RSA key container is available machine-wide, it does not mean that any arbitrary account can access it. The required step of granting Read access makes this approach secure and effective. It is reasonably simple to set up, and it allows you to isolate configuration data between applications. As you will see in the next section on useMachineContainer, RSA key containers that are usable machine-wide are really the only viable mechanism for providing configuration isolation to ASP.NET applications. Creating a RSA key container can be accomplished with the following command line: aspnet_regiis -pc “Application_A_Container” This command creates a new RSA key container called Application_A_Container that is accessible machine-wide assuming the appropriate access control lists (ACLs) are granted. As an aside, the -pc option supports an additional -size option that allows you to specify how large you want the RSA key to be. By default, the tool will create 1024-bit keys, but the RSA standard supports keys as large as 16,384 bits if necessary. You grant access to the newly created container using the -pa switch, as shown a little bit earlier. For this to make sense, though, you must separate your applications into separate worker processes running as something other than NETWORK SERVICE. Obviously, granting key container access to NETWORK SERVICE is pointless if your intent is to isolate access by worker process identity. Assuming that you use a different identity for each of your worker processes, you can use the -pa switch to grant access in such a way that each new key container is accessible by only a specific worker process account. This approach does have a similar manageability issue to using keyEntropy with the DPAPI provider. Using a different key container per process identity means that you have to create a different RSA provider definition for each separate key container. However, you do not have to worry about where you place the different RSA provider definitions. Even if applications are able to physically read protected configuration definitions for other applications, the key container ACLs will prevent applications running with different identities from successfully decrypting other application’s configuration sections. useMachineContainer As with the DPAPI provider, the RSA provider allows you to use a per-user mode of operation. The previous discussions on the RSA provider have been using key containers that are visible machinewide. For an additional level of security, you might think that you could create key containers that are only “visible” to specific user accounts. This approach is dependent on Windows user profiles as you will see in a bit. The first step is to define a protected configuration provider to use a user-specific key container. Something like the following: Download at Boykma.Com 271 Chapter 5: Configuration System Security After you have a provider defined, the general sequence of steps enables you to use user-specific containers: 1. Open a command window running as the user account that will “own” the key container. You can log in interactively as the account or use the runas command. 2. Use the aspnet_regiis -pc command to create a key container. 3. Use aspnet_regiis -pe to encrypt the desired configuration sections. You need to perform the encryption while running as the specific user account; otherwise, the configuration system is not going to be using the correct user-specific key container. Make sure to use the -prov option so that the tool knows to use the appropriate provider definition. 4. Log off or close the spawned command window. 5. Change the identity of your web application’s application pool to the same identity that was used to create the key container and encrypt the configuration sections. When you run your web application, it will be able to decrypt the encrypted configuration sections using the key pair located in the user-specific key container. Unfortunately, this entire process suffers from the same dependency on Windows user profiles as DPAPI. If you reboot the machine, causing the user profile that was loaded in step 1 to go away, your web application can no longer decrypt the configuration section. As with DPAPI the per-user key containers are not really usable in ASP.NET applications; you need to stick with machine-wide containers and selectively ACL the RSA key containers to get configuration isolation across applications. Synchronizing Key Containers across Machines The biggest advantage of the RSA provider over the DPAPI provider is that RSA provides a viable approach for synchronizing the contents of a key container across a web farm. Unlike DPAPI, RSA key pairs are exportable. The most important thing you need to do to ensure that you can synchronize keys is create your key containers so that they are exportable. The following command uses the -exp option to create a machine-wide key container with exportable keys. If you forget the -exp option the resultant key container will not be exportable. Note that for this discussion, only machine-wide key containers are used because per-user key containers aren’t really suitable for ASP.NET. aspnet_regiis -pc ExportableContainer -size 2048 -exp The next step is to export the key material so that it can be physically transported. The aspnet_regiis command line for export is shown here: aspnet_regiis -pri -px ExportableContainer c:\exportedkey.xml The -px option tells the tools that the key information in the container should be exported to the file name shown on the command line. The bold -pri option is important because it also tells the tool to ensure that the private half of the RSA key pair is exported as well. If you forget to export the private key, when you import the result on another server it will be useless because you need the private half of the key pair to be able to decrypt the 3DES encryption key from the XML in the protected configuration section. 272 Download at Boykma.Com Chapter 5: Configuration System Security With the export file in hand, you can go to each machine that needs to share the key material and import the key container with the following command: aspnet_regiis -pi ExportableContainer c:\exportedkey.xml The -pi command tells the tool to import the contents of the XML file into the specified RSA key container. After you import the file on any given machine, you should immediately delete it and wipe the directory that contained it. It would be a major security breach if the XML export file is left lying around for someone to copy and walk away with. The same holds true for the machine where the original export occurred; you should also ensure that the original export file is not lying around on disk waiting for someone to snoop. As a last step, because this approach creates a new key container upon import, you need to use aspnet_regiis with the -pa switch on each web server to grant Read access on the key container to the appropriate worker process accounts. At this point you have a key container called ExportableContainer on one or more machines. In a really secure web environment, you can perform the encryption of your configuration sections using a system that is not directly connected to the internet. After you create a config file with all of the appropriate encrypted configuration sections, you copy the result to all of the machines in your web farm. The previous steps of importing containers and ACLing the containers are one-time setup tasks. After they have been accomplished, you only need to copy encrypted configuration files to all of your web servers. This is a much cleaner approach than using DPAPI, where you would need to perform in-place encryption on each of your production web servers. In-place encryption is not only error-prone, but it also means the web server administrator always gets to see the before image of the configuration data. With the RSA provider, you can go so far as having a security group responsible for encrypting your production configuration files; the security group members could be the only ones that know sensitive information such as connection string passwords. Then when the security group is done with the encryption process they could hand the results back to your development team for deployment onto a production farm. In this way, only a small set of individuals actually knows the sensitive pieces of cleartext configuration information. aspnet_regiis Options Several different command-line options have been thrown around for aspnet_regiis. The following table briefly summarizes the main options that have been used for the various samples. Each of these options usually has additional suboptions for things like per-user RSA containers, more specific virtual path information, and so on. However, the table shows only the most common options that you are likely to need: Command-Line Option -pc Container_Name -exp -size 4096 Description Creates a new RSA key container that is available to any account, assuming Read access is granted. If you plan to export the key container, you need to include the -exp option. The -size option lets you specify the size of the RSA key that will be created in the container. Continued Download at Boykma.Com 273 Chapter 5: Configuration System Security Command-Line Option -pa Container_Name “DOMAIN\user” -pri -px Container_ Name file name -pi Container_Name file name -pe config_section_ path -app /app_path -prov provider_name -pd config_section_ path -app /app_path Description Grants Read access on an RSA key container to the specified user account. Exports an RSA key container to the specified file. The export file includes the private RSA key information as well. Imports an RSA key container. Encrypts the configuration section identified by the configuration section path. This path looks something like system.web/membership. The application path specified by -app denotes a virtual path within the default web site, unless you specify a site with the -site option. The encryption uses the provider specified by -prov. This provider must have been defined in the section. If you want to use the default protected configuration provider, then -prov is not necessary. Decrypts the configuration section identified by the configuration section path. This path looks something like system.web/membership. The application path specified by -app denotes a virtual path within the default web site, unless you specify a site with the -site option. The aspnet_regiis tool really has only two modes of operation when working with protected configuration providers: ❑❑ The tool has rich support for the RSA-based provider that ships in the framework. aspnet_regiis includes many configuration switches to carry out various operations that are specific to the RSA-based provider. ❑❑ The tool can invoke any arbitrary provider, but it cannot support any special behavior that may be required by the provider. You can see that the command line (the -pe and -pd options) does not include any special switches beyond the basics that are required to identify a specific configuration section to protect. This means that if you use a different protected configuration provider, and if you need to support special operations related to that provider (for example, the key container setup required when using RSA), you will need to write your own code to carry out these types of provider-specific tasks. Using Protected Configuration Providers in Partial Trust You have seen how protected configuration works transparently with the features that depend on the underlying configuration data. However, because protected configuration relies on providers, and these providers are public, there is not anything preventing you from just creating an instance of either the 274 Download at Boykma.Com Chapter 5: Configuration System Security RSA or the DPAPI provider and calling the methods on these providers directly. The Decrypt method on a ProtectedConfigurationProvider accepts a System.Xml.XmlNode as an input parameter and returns the decrypted version as another XmlNode instance. Combining the simplicity of this method with the fact that most ASP.NET trust levels allow some Read access to the file system means that malicious developers could potentially attempt the following steps: 1. Open the application’s web.config file as a text file or through a class like System.Xml​ .XmlTextReader. 2. Get a reference to the appropriate DPAPI or RSA provider based on the provider name in the configProtectionProvider attribute that is on the configuration element being protected. 3. Pass the contents of the element for a protected configuration section to the Decrypt method of the protected configuration provider obtained in the previous step. In some scenarios, you do not want any piece of code to be able to accomplish this. Even in High trust where your code has access to read the machine.config and root web.config files, you probably do not want this loophole to exist. If a feature is written to mirror configuration properties in a public API, then that is where developers should access the values. In some cases, if you author a feature so that certain pieces of configuration information are read, but are never exposed from a feature API, then you do not want random code that outflanks your feature and decrypts sensitive data directly from configuration. To prevent this, the DPAPI and the RSA providers include the following class-level demand on their class signatures: [PermissionSet(SecurityAction.Demand, Name=”FullTrust”)] This declarative demand requires that all callers up the call stack must be running in Full trust. The FullTrust value for the Name property is actually a reference to one of the built-in .NET Framework permission sets that you can see if you use a tool like the .NET Framework Configuration MMC. As a result, all code in the call stack needs to be running in the GAC or the entire ASP.NET application needs to be running in the ASP.NET Full trust level. For a partial trust application, any attempt to directly call the providers will fail with a SecurityException. You can see how this works by writing some sample code to load an application’s web.config file, extract an encrypted section out of it, and then pass it to the correct provider. C# using System.Configuration; using System.Xml; … protected void Page_Load(object sender, EventArgs e) { XmlDocument xd = new XmlDocument(); xd.Load(Server.MapPath(“~/web.config”)); Download at Boykma.Com 275 Chapter 5: Configuration System Security XmlNamespaceManager ns = new XmlNamespaceManager(xd.NameTable); ns.AddNamespace(“u”, “http://schemas.microsoft.com/.NetConfiguration/v2.0”); XmlNode ec = xd.SelectSingleNode(“//u:configuration/u:system.web/u:machineKey”,ns); RsaProtectedConfigurationProvider rp = (RsaProtectedConfigurationProvider) ProtectedConfiguration.Providers[“AppSpecificRSAProvider”]; XmlNode dc = rp.Decrypt(ec); } VB.NET Imports System.Configuration Imports System.Xml … Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) _ Handles Me.Load Dim xd As New XmlDocument() xd.Load(Server.MapPath(“~/web.config”)) Dim ns As New XmlNamespaceManager(xd.NameTable) ns.AddNamespace(“u”, “http://schemas.microsoft.com/.NetConfiguration/v2.0”) Dim ec As XmlNode = _ xd.SelectSingleNode(“//u:configuration/u:system.web/u:machineKey”, _ ns) Dim rp As RsaProtectedConfigurationProvider = _ CType(ProtectedConfiguration.Providers(“AppSpecificRSAProvider”), _ RsaProtectedConfigurationProvider) Dim dc As XmlNode = rp.Decrypt(ec) End Sub The sample code uses an XPath query to extract an XmlNode reference to the encrypted section. It then uses the ProtectedConfiguration class to get a reference to the correct provider for decryption. If you run this code in a Full trust ASP.NET application it will work. However, if you drop the trust level to High or lower, a SecurityException occurs when the call to Decrypt occurs. Even though the protected configuration providers demand Full trust, you can still protect your own custom configuration sections in partial trust applications when using either the DPAPI or the RSA providers. At runtime when a call is made to GetSection from ConfigurationManager or WebConfiguration​ Manager, internally the configuration system asserts Full trust on your behalf prior to decrypting the contents of your custom configuration section. This behavior makes sense because the assumption is that if a piece of code can successfully call GetSection (for example, if ConfigurationPermission has been granted to the partial trust application, or requirePermission has been set to false, or your code is running in the GAC and asserts ConfigurationPermission), there is no reason why access to configuration via a strongly typed configuration class should fail even if the underlying data requires decryption. 276 Download at Boykma.Com Chapter 5: Configuration System Security If you have a sample application running in High trust (High trust is necessary for this sample because the “runtime” configuration APIs fail by default when called below High trust), you can attempt to open the protected section with the following code: C# MachineKeySection mk = (MachineKeySection)WebConfigurationManager.GetSection(“system.web/machineKey”); VB.NET Dim mk As MachineKeySection = _ CType(config.GetSection(“system.web/machineKey”), _ MachineKeySection) The preceding code will work in both High and Full trust. In High trust, the code succeeds because it makes it over the hurdle of the two following security checks: ❑❑ The application is running in High trust, so the configuration system demand for Configuration​ Permission succeeds. ❑❑ The configuration system internally asserts Full trust when deserializing the configuration section, so the declarative security demand from the protected configuration provider passes as well. However, if you use the design-time configuration API as follows in High trust, the same logical operation fails: C# //This will fail in High trust or below with a protected config section Configuration config = WebConfigurationManager.OpenWebConfiguration(“~”); MachineKeySection mk = (MachineKeySection)config.GetSection(“system.web/machineKey”); VB.NET ‘These two pieces of code fail in partial trust when using protected config ‘because Open is a “design time” API Dim config As Configuration = WebConfigurationManager.OpenWebConfiguration(“~”) Dim mk As MachineKeySection = _ CType(config.GetSection(“system.web/machineKey”), _ MachineKeySection) In this scenario, three security checks occur, and the last one fails: ❑❑ The configuration system opens the file using file I/O, which generates a FileIOPermission demand. The demand passes because High trust has rights to read all configuration files in the inheritance chain. ❑❑ The NTFS ACLs on machine.config, root web.config, and the application’s web.config also allow Read access. Download at Boykma.Com 277 Chapter 5: Configuration System Security ❑❑ The protected configuration provider demands Full trust. The demand fails because the sample code is running in the Page_Load method of a partial trust ASP.NET application. Internally, the configuration does not assert Full trust on your behalf when calling the Open* methods. The interaction of trust levels with protected configuration can be a bit mind-numbing to decipher. Excluding intervention on your part with configuration files or sandboxed GAC assemblies, the following list summarizes the behavior of the RSA and DPAPI protected configuration providers: ❑❑ Protected configuration providers work in partial trust applications that load configuration using the GetSection method. This method is the normal way a custom feature that you author would load configuration. ❑❑ Protected configuration providers fail in partial trust when using the design-time configuration APIs (that is, the various Open* methods). Normally, you will not call these methods from anything other than administrative applications or command-line configuration tools. Redirecting Configuration with a Custom Provider So far, all of the discussion on protected configuration has revolved around the idea of encrypting and decrypting configuration sections. Given the feature’s heritage with the old aspnet_setreg.exe tool, this is understandable. Traditionally, when customers asked for a way to secure sensitive pieces of configuration data, they were looking for a way to encrypt the information. However, there is no reason that the concept of “protection” can’t be interpreted differently. A common problem some of you probably have with your web applications is with promoting an application through various environments. Aside from development environments you may have test servers, staging servers, live production servers, and potentially warm backup servers. Encrypting your configuration data does make it safer, but it also increases your management overhead in attempting to synchronize configuration data properly in each of these environments. This overhead is even more onerous if you work in a security-sensitive environment where only a limited number of personnel are allowed to encrypt the final configuration information prior to pushing it into production. Protected configuration is probably manageable with manual intervention for a few servers and is tolerable with the help of automated scripts in environments that deal with dozens if not hundreds of servers. However, you can kill two birds with one stone if you think about “protected” actually being a problem of getting important configuration data physically off your web servers. If you store selected configuration sections in a central location (such as a central file share or a central configuration database), you have a more manageable solution and, depending on how you implement this, a more secure solution as well. You can write a custom protected configuration provider that determines information about the current server and the currently running application. Because a protected configuration provider controls the format of the data that is written into a protected configuration section, you can store any additional information you need in this format. For example, you could have a custom XML format that includes hints to your provider so that it knows if a configuration section for machine.config, the root web.config, or an application web.config is requested. Even though the DPAPI and RSA providers use the W3C XML Encryption Recommendation, this is not a strict requirement for the format of encrypted data that is used by a custom provider. 278 Download at Boykma.Com Chapter 5: Configuration System Security A custom provider can then reach out to a central repository of configuration information and return the appropriate information. Depending on how stringent your security needs are you can layer extra protection in the form of transport layer security (such as an SSL connection to a SQL Server machine as well as IPSEC connection rules) and encrypt the configuration data prior to storing it in a central location. When you have a select group of individuals who manage the configuration data for live production servers, it is probably much easier to have such a group manage updates to a single database as opposed to encrypting a file and then having to worry about getting the synchronization of said file correct across multiple machines. Implementing a custom protected configuration provider requires you to derive from the System​ .Configuration.ProtectedConfigurationProvider class. As you can see, the class signature is very basic: C# public abstract class ProtectedConfigurationProvider : ProviderBase { public abstract XmlNode Encrypt(XmlNode node); public abstract XmlNode Decrypt(XmlNode encryptedNode); } VB.NET Public MustInherit Class ProtectedConfigurationProvider Inherits ProviderBase Public MustOverride Function Decrypt(ByVal encryptedNode As XmlNode) As XmlNode Public MustOverride Function Encrypt(ByVal node As XmlNode) As XmlNode End Class For a sample provider that demonstrates redirecting configuration to a database, you implement only the Decrypt method because this is the method used at runtime to return configuration data to the caller. If you store more complex data inside your protected configuration format, implementing the Encrypt method will make life easier when storing configuration sections in a custom data store. First look at what a “protected” configuration section in a web.config file will look like using the custom provider: As with previous snippets of protected configuration, the section references a protected configuration provider. Instead of the actual definition of the section though, the element is common to all protected configuration sections. However, what is enclosed within this element is determined by each provider. In this case, to keep the sample provider very simple, the protected data consists of only a single element: a element. Download at Boykma.Com 279 Chapter 5: Configuration System Security Unlike protected configuration providers that blindly encrypt and decrypt data, this provider needs to know the actual configuration section that is being requested. The RSA and DPAPI providers actually have no idea what they are operating against. Both providers work against a fixed schema and consider the encrypted blob data to be opaque from a functionality standpoint. The custom provider, however, needs to know what section is really being requested because its purpose is to store configuration data in a database for any arbitrary configuration section. The name attribute within the element gives the custom provider the necessary information. Although this is just a basic example of what you can place with , you can encapsulate any kind of complex data your provider may need within the XML. The custom provider will store configuration sections in a database, keying off of a combination of the application’s virtual path and the configuration section. The database schema that follows shows the table structure for storing this: create table ConfigurationData ( ApplicationName nvarchar(256) NOT NULL, SectionName nvarchar(150) NOT NULL, SectionData ntext ) go alter table ConfigurationData add constraint PKConfigurationData PRIMARY KEY (ApplicationName,SectionName) go Retrieving this information will similarly be very basic with just a single stored procedure pulling back the SectionData column that contains the raw text of the requested configuration section: create procedure RetrieveConfigurationSection @pApplicationName nvarchar(256), @pSectionName nvarchar(256) as select SectionData from ConfigurationData where ApplicationName = @pApplicationName and SectionName = @pSectionName go Because the custom protected configuration provider needs to connect to a database, a connection string must be included within the definition of the provider. Writing and configuring custom providers is the subject of Chapter 10; the important point for this sample is that ASP.NET allows you to add arbitrary information to the configuration element for providers. 280 Download at Boykma.Com Chapter 5: Configuration System Security The provider configuration looks similar to the configurations for the RSA and DPAPI providers. In this case, however, the custom provider requires a connectionStringName element so that it knows which database and database server to connect to. The value of this attribute is simply a reference to a named connection string in the section, as shown here: When creating your own custom providers, you have the freedom to place any provider-specific information you deem necessary in the element. Now that you have seen the data structure and configuration related information, take a look at the code for the custom provider. Because a protected configuration provider ultimately derives from System.Configuration.Provider.ProviderBase, the custom provider can override portions of ProviderBase as well as ProtectedConfigurationProvider. Chapter 10 goes into more detail on ProviderBase; for now, though, the custom provider will override ProviderBase.Initialize so that the provider can retrieve the connection string from configuration: C# using System; using System.Data; using System.Data.SqlClient; using System.Configuration; using System.Configuration.Provider; using System.Web; using System.Web.Hosting; using System.Web.Configuration; using System.Xml; namespace CustomProviders { public class DatabaseProtectedConfigProvider : ProtectedConfigurationProvider { private string connectionString; public DatabaseProtectedConfigProvider() { } public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config) { string connectionStringName = config[“connectionStringName”]; if (String.IsNullOrEmpty(connectionStringName)) throw new ProviderException(“You must specify “ + “connectionStringName in the provider configuration”); connectionString = WebConfigurationManager.ConnectionStrings[connectionStringName] _ .ConnectionString; if (String.IsNullOrEmpty(connectionString)) throw new ProviderException(“The connection string “ + Download at Boykma.Com 281 Chapter 5: Configuration System Security “could not be found in .”); config.Remove(“connectionStringName”); base.Initialize(name, config); } //Remainder of provider implementation } } VB.NET Imports System Imports System.Data Imports System.Data.SqlClient Imports System.Configuration Imports System.Configuration.Provider Imports System.Web Imports System.Web.Hosting Imports System.Web.Configuration Imports System.Xml Namespace CustomProviders Public Class DatabaseProtectedConfigProvider Inherits ProtectedConfigurationProvider Private connectionString As String Public Sub New() End Sub Public Overrides Sub Initialize( _ ByVal name As String, _ ByVal config As System.Collections.Specialized.NameValueCollection) Dim connectionStringName As String = config(“connectionStringName”) If String.IsNullOrEmpty(connectionStringName) Then Throw New ProviderException( _ “You must specify connectionStringName in the provider configuration”) End If connectionString = _ WebConfigurationManager.ConnectionStrings(connectionStringName).ConnectionString If String.IsNullOrEmpty(connectionString) Then Throw New ProviderException( _ “The connection string could not be found in .”) End If config.Remove(“connectionStringName”) MyBase.Initialize(name, config) End Sub ‘Remainder of provider implementation End Class End Namespace 282 Download at Boykma.Com Chapter 5: Configuration System Security The processing inside of the Initialize method performs a few sanity checks to ensure that the connectionStringName attribute was specified in the provider’s element, and that furthermore the name actually points at a valid connection string. After the connection string is obtained from the ConnectionStrings collection, it is cached internally in a private variable. Of course, the interesting part of the provider is its implementation of the Decrypt method: C# public override XmlNode Decrypt(XmlNode encryptedNode) { //Application name string applicationName = HostingEnvironment.ApplicationVirtualPath; XmlNode xn = encryptedNode.SelectSingleNode(“/EncryptedData/sectionInfo”); //Configuration section to retrieve from the database string sectionName = xn.Attributes[“name”].Value; using (SqlConnection conn = new SqlConnection(connectionString)) { SqlCommand cmd = new SqlCommand(“RetrieveConfigurationSection”, conn); cmd.CommandType = CommandType.StoredProcedure; SqlParameter p1 = new SqlParameter(“@pApplicationName”, applicationName); SqlParameter p2 = new SqlParameter(“@pSectionName”, sectionName); cmd.Parameters.AddRange(new SqlParameter[] { p1, p2 }); conn.Open(); string rawConfigText = (string)cmd.ExecuteScalar(); conn.Close(); //Convert string from the database into an XmlNode XmlDocument xd = new XmlDocument(); xd.LoadXml(rawConfigText); return xd.DocumentElement; } } VB.NET Public Overrides Function Decrypt( _ ByVal encryptedNode As XmlNode) As XmlNode() Dim applicationName As String = _ HostingEnvironment.ApplicationVirtualPath Dim xn As XmlNode = _ encryptedNode.SelectSingleNode(“/EncryptedData/sectionInfo”) Dim sectionName As String = xn.Attributes(“name”).Value Using conn As New SqlConnection(connectionString) Dim cmd As New SqlCommand(“RetrieveConfigurationSection”, conn) cmd.CommandType = CommandType.StoredProcedure Dim p1 As New SqlParameter(“@pApplicationName”, applicationName) Dim p2 As New SqlParameter(“@pSectionName”, sectionName) Download at Boykma.Com 283 Chapter 5: Configuration System Security cmd.Parameters.AddRange(New SqlParameter() {p1, p2}) conn.Open() Dim rawConfigText As String = CStr(cmd.ExecuteScalar()) conn.Close() ‘Convert string from the database into an XmlNode Dim xd As New XmlDocument() xd.LoadXml(rawConfigText) Return xd.DocumentElement End Using End Function The Decrypt method’s purpose is take information about the current application and information available from the element and use it to retrieve the correct configuration data from the database. The provider determines the correct application name by using the System.Web.Hosting.Hosting​ Environment class to determine the current application’s virtual path. The name of the configuration section to retrieve is determined by parsing the section to get to the name attribute of the custom element. With these pieces of data the provider connects to the database using the connection string supplied by the provider’s configuration section. The configuration data stored in the database is just the raw XML fragment for a given configuration section. For this example, which stores a section in the database, the database table just contains the text of the section’s definition taken from machine.config stored in an ntext field in SQL Server. Because protected configuration providers work in terms of XmlNode instances, and not raw strings, the provider converts the raw text in the database back into an XmlDocument, which can then be subsequently returned as an XmlNode instance. Because the data in the database is well-formed XML, the provider can just return the DocumentElement for the XmlDocument. The provider’s implementation of the Encrypt method is just stubbed out. For your own custom providers, you could implement the inverse of the logic shown in the Decrypt method that would scoop the configuration section out of the config file and stored in the database. C# public override XmlNode Encrypt(XmlNode node) { throw new NotImplementedException(“This method is not implemented.”); } VB.NET Public Overrides Function Encrypt(ByVal node As XmlNode) As XmlNode Throw New NotImplementedException(“This method is not implemented.”) End Function What is really powerful about custom protected configuration providers is that you can go back to some of the sample configuration code used earlier in the chapter and run it, with the one change being that you use the “protected” configuration section for . 284 Download at Boykma.Com Chapter 5: Configuration System Security C# MembershipSection ms = (MembershipSection)ConfigurationManager.GetSection(“system.web/membership”); VB.NET Dim ms As MembershipSection = _ CType(config.GetSection(“system.web/membership”), _ MembershipSection) This code works unchanged after you swap in the new section using the custom protected configuration provider. This is exactly what you would want from protected configuration. Nothing in the application code needs to change despite the fact that now the configuration section is stored remotely in a database as opposed to locally on the file system. Clearly, the sample provider is pretty basic in terms of what it supports. However, with a modicum of work you could extend this provider to support features like the following: ❑❑ Machine-specific configuration ❑❑ Environment-specific configuration, separating data by terms like TEST, DEV, PROD, and so on ❑❑ Encrypting the actual data inside of the database so that database administrators can’t see what is stored in the tables Nothing requires you to store configuration data in a traditional data store like a database or on the file system. You could author a custom provider that uses a Web Service call or socket call to a configuration system as opposed to looking up data in a database. One caveat to keep in mind with custom protected configuration providers is that after the data is physically stored outside of a configuration file, ASP.NET is no longer able to automatically trigger an app-domain restart whenever the configuration data changes. With the built-in RSA and DPAPI providers, this is not an issue because the encrypted text is still stored in web.config and machine.config files. ASP.NET listens for change notifications and triggers an app-domain restart in the event any of these files change. However, ASP.NET does not have a facility to trigger changes based on protected configuration data stored in other locations. For this reason, if you do write a custom provider along the lines of the sample provider, you need to incorporate operational procedures that force app-domains to recycle whenever you update configuration data stored in locations other than the standard file-based configuration files. Summary Configuration security in ASP.NET 2.0 included quite a number of improvements that ASP.NET 3.5 builds on top of them. While the original -based locking approach is still supported (and is definitely still useful), ASP.NET 3.5’s configuration system now gives you the ability to enforce more granular control over individual sections. The lockAttributes attribute restricts the ability of child configuration files to override selected attributes defined on the parent. The lockElements attribute prevents entire configuration elements from being redefined in child configuration files. Both of these attributes support an alternate syntax to make it easier to configure fine-grained security when many attributes or many nested configuration elements need to be controlled. Download at Boykma.Com 285 Chapter 5: Configuration System Security In addition, IIS 7.0 ships with the Feature Delegation feature that allows administrators to decide which configuration sections of the configuration section group located in the ApplicationHost.config configuration file can be edited by developers through the application’s web.config file. It is the IIS 7.0 way of protecting configuration settings in the ApplicationHost​ .config file. Because configuration data exists within physical files, NTFS permissions come into play when reading or writing configuration data. Under normal conditions, configuration data only needs to be read; although it has to be read up the entire inheritance chain from the most derived web.config file all the way up to the root web.config and web.config files. Because ASP.NET reads runtime configuration data using the process account or application impersonation identity, reading configuration usually succeeds assuming the file ACLs have been set up properly. Physically writing configuration data is something that should be reserved only for administrative-style applications or command-line tools due to the need for Full Control on these files. ASP.NET also supports remote editing of configuration files, although for security reasons this functionality is turned off by default. Because ASP.NET supports running in partial trust, the configuration system makes use of the Framework’s CAS support to limit what can be done in partial trust. Access to strongly typed configuration sections is allowed only in High and Full trust. If you need to access the configuration classes directly in Medium trust or lower, you will need to use the requirePermission attribute. For the built-in configuration sections, you should avoid doing so because most ASP.NET features expose public APIs that already give access to most of the configuration data you need. Customers have long asked for the ability to secure configuration data so that prying eyes cannot see sensitive information such as database connection strings. The protected configuration feature that was introduced in .NET Framework 2.0 allows you to encrypt configuration sections using either DPAPI or RSA. Because the protected configuration feature is based on the provider model, you also have the option to write or purchase custom protected configuration providers. This gives you the freedom to implement different encryption strategies or, as seen with the sample provider, different storage locations for your configuration data. 286 Download at Boykma.Com 6 Forms Authentication Forms authentication is the most widely used authentication mechanism for Internet-facing ASP.NET sites. The appeal of forms authentication is that sites with only a few pages and simple authentication requirements can make use of forms authentication, and complex sites can still rely on forms authentication for the basic handling of authenticating users. ASP.NET 3.5 continues to use the same forms authentication that was improved in ASP.NET 2.0, with some enhancements that allow the integration of forms authentication on IIS 7.0 so that not only ASP.NET resources can be authenticated, but also other types of content. Moreover, the ASP.NET 3.5 runtime resembles that of ASP.NET 2.0, with additional features. This chapter covers the following topics on ASP.NET 3.5 forms authentication: ❑❑ Reviewing how forms authentication works in the HTTP pipeline (most of this was covered in Chapter 3). ❑❑ Making changes to the behavior of persistent forms authentication tickets. ❑❑ Securing the forms authentication payload. ❑❑ Securing forms authentication cookies with HttpOnly and requireSSL. ❑❑ Using cookieless support in forms authentication. ❑❑ Using forms authentication across ASP.NET 1.1 and ASP.NET 3.5. ❑❑ Using forms authentication across different content types. ❑❑ Leveraging the UserData property of FormsAuthenticationTicket. ❑❑ Passing forms authentication tickets between applications. ❑❑ Enforcing a single login and preventing replayed tickets after logout. Download at Boykma.Com Chapter 6: Forms Authentication A Quick Recap of Forms Authentication In Chapter 3, the sections on AuthenticateRequest, AuthorizeRequest and EndRequest described how forms authentication works throughout the HTTP pipeline. In summary, forms authentication performs the following tasks: 1. During AuthenticateRequest, the FormsAuthenticationModule checks the validity of the forms authentication ticket (carried in a cookie or in a cookieless format on the URL) if one exists. If a valid ticket is found, this results in a GenericPrincipal referencing a FormsIdentity as the value for HttpContext.Current.User. The actual information in the ticket is available as an instance of a FormsAuthenticationTicket off of the FormsIdentity. 2. During AuthorizeRequest, other modules and logic such as the UrlAuthorizationModule attempt to authorize access to the currently requested URL. If an authenticated user was not created earlier by the FormAuthenticationModule, any URL that requires some type of authenticated user will fail authorization. However, even if forms authentication created a user, authorization rules that require roles can still fail unless you have written custom logic to associate a FormsIdentity with a set of roles or used a feature like Role Manager that performs this association automatically. 3. If authorization fails during AuthorizeRequest, the current request is short-circuited and immediately forwarded to the EndRequest phase of the pipeline. The FormsAuthentication​ Module runs during EndRequest and if it detects that Response.StatusCode is set to 401, the module automatically redirects the current request to the login page that is configured for forms authentication (login.aspx by default). This basic summary of forms authentication demonstrates that the forms authentication ticket is the piece of persistent authentication information around which the forms authentication feature revolves. The next few sections delve into more details about how the forms authentication ticket is protected, persisted, and passed around applications. For all practical purposes, developers use the terms “forms authentication ticket” and “forms authentication cookie” interchangeably. Understanding Persistent Tickets Since ASP.NET 1.0, the forms authentication feature has supported persistent and nonpersistent tickets. In ASP.NET 1.0 and 1.1 the forms authentication ticket was always stored in a cookie (again excluding the Mobile Internet Toolkit which most developers probably have not used). So, the decision between using a persistent versus nonpersistent ticket is a choice between using persistent or session-based cookies. The lifetime of a session-based cookie is the duration of the interactive browser session; when you shut down the browser, any session-based cookies held in memory are gone. The forms authentication feature included the option for persistent cookies to enable lower-security applications (message boards, personal websites with minimal security requirements, and so on) to store a representation of the authenticated user without constantly requiring users to log in again. Clearly, for some sites where users infrequently access the application (and hence are always forgetting their credentials), persistent cookies are a great usability enhancement. The one “small” problem is that on ASP.NET 1.0 and ASP.NET 1.1 sites, persistent cookies are given a 50-year lifetime. Now, I am all for making certain types of websites easier to use (like everybody else I have an idiotic number 288 Download at Boykma.Com Chapter 6: Forms Authentication of username-password combinations to deal with), but I think 50 years is pushing it a bit! You can see this for older ASP.NET sites that issue cookies if you take a look at the expiration date for their forms authentication tickets. For example, the following code issues a persistent ticket: C# FormsAuthentication.RedirectFromLoginPage(“testuser”, true); VB.NET FormsAuthentication.RedirectFromLoginPage(“testuser”, True) The resulting expiration date on the cookie when I was writing this was “4/5/2058 11:18:25 AM.” The net result is that a digitally encrypted and digitally signed forms authentication ticket is left lying around a user’s computer until by happenstance the cookie is deleted. On one hand, if you regularly delete cookies, then 50-year lifetimes are probably not a big deal. On the other hand, as a website developer you definitely can bet that some percentage of your user population is accruing cookies ad infinitum. From a security perspective the 50-year lifetime is really, really bad. Although the default security for forms authentication cookies encrypts and signs the cookies, it is likely that sometime in the next 50 years computing power will have reached a point that the present-day forms authentication ticket can be cracked in a reasonably short time. It’s unlikely that anybody will ever have their original computer from 50 years ago (where would you put that old UNIVAC today?). But some website users will still be on the same machine 5 to 7 years later, and if they regularly visit the same site, the forms authentication ticket issued years earlier will still be lying around waiting to be hijacked and cracked. As a result of this type of security concern with excessively long-lived forms authentication tickets, in ASP.NET 3.5, as in ASP.NET 2.0, persistent cookies set their expiration based upon the value of the cookie timeout set in configuration. Taking the same code shown earlier and running it on ASP.NET 3.5 with the default cookie timeout of 30 minutes results in a persistent cookie that expires 30 minutes later (you can see this if you view the files in your browser cache and look for the cookie file). This change may take a number of developers by surprise, and their first inkling of the new behavior may be complaints from website users suddenly being forced to login. However, even though the ASP.NET 2.0 and ASP.NET 3.5 behavior changes the cookie expiration for new cookies issued using forms authentication, the new behavior has no effect on preexisting cookies. If you upgrade an ASP.NET 1.1 application to ASP.NET 3.5, any users with 50-year cookies floating around will continue to retain these cookies. Even if you use sliding expiration for your forms authentication tickets, because ASP.NET has not been around for 25 years, none of the preexisting persistent cookies will be reissued due to time passing for sliding expirations (forms authentication attempts to reissue a cookie when 50 percent or more of the configured cookie timeout has elapsed). This raises the question of whether developers should take explicit steps to reissue their persistent cookies with more reasonable timeouts. I believe that a little more security is better than 50-year cookie lifetimes, and recommend that developers using persistent forms authentication cookies add some logic to their applications after upgrade. First, developers should determine a reasonable persistent cookie timeout. This may be a few weeks or months, although I wouldn’t recommend going beyond one year. Even for sites that do not care too much about security, it does not seem unreasonable to ask people to reauthenticate themselves once a year. Download at Boykma.Com 289 Chapter 6: Forms Authentication ASP.NET 2.0 and ASP.NET 3.5 have only one cookie timeout setting (the timeout attribute in the configuration element). If your site needs to issue a mixture of persistent and session-based cookies, both types of cookies will use the timeout set in configuration; however, expiration enforcement happens through different mechanisms. In these situations it makes sense to ask why a website (or perhaps a set of websites) mixes the comparatively insecure persistent cookie option with session-based forms authentication tickets. Cookie-based websites should use one type of cookie persistence for all website users, and stick with a single persistence model. After you have determined a new value for timeout, the next step is to add some code to your site that automatically swaps out the old persistent cookie for a new one with an updated expiration. Post​ AuthenticateRequest is a convenient point to perform this work. The following code for global​ .asax shows how this can be accomplished. C# void Application_PostAuthenticateRequest(Object sender, EventArgs e) { if (User.Identity is FormsIdentity) { if (((FormsIdentity)User.Identity).Ticket.Expiration > (DateTime.Now.Add(new TimeSpan(0,40320,0)))) { FormsAuthentication.RedirectFromLoginPage(User.Identity.Name, true); } } } VB.NET Private Sub Application_PostAuthenticateRequest( _ ByVal sender As Object, _ ByVal e As EventArgs) If TypeOf User.Identity Is FormsIdentity Then If (CType(User.Identity, FormsIdentity)).Ticket.Expiration > _ (DateTime.Now.Add(New TimeSpan(0,40320,0))) Then FormsAuthentication.RedirectFromLoginPage(User.Identity.Name, True) End If End If End Sub The code first checks to see whether an authenticated FormsIdentity exists on the current context. If one exists, the Identity that is available from the User property on the context is cast to a Forms​Identity so that you can get access to the FormsAuthenticationTicket available off of the Ticket property. The FormsAuthenticationTicket conveniently exposes its expiration with the Expiration property. In the sample code, if the ticket expires more than 40320 minutes (roughly one month) from now, the credentials are reissued as a persistent ticket. Running this code on ASP.NET 3.5 results in a forms authentication cookie being reissued with the updated behavior for computing cookie expiration based on the timeout attribute in configuration. One thing to note is that the forms authentication API does not expose the value of the timeout attribute in a convenient manner. Although you could technically use the strongly typed configuration classes in ASP.NET 3.5 to get the correct value, you cannot really depend on that approach if you plan to run in partial trust (more on issues with strongly typed configuration classes and partial trust in Chapter 5). 290 Download at Boykma.Com Chapter 6: Forms Authentication As a result, the somewhat simplistic workaround is to duplicate the expiration value either by hardcoding it as in the sample code or, for better maintenance, by storing it as a value in a place like the section in configuration. How Forms Authentication Enforces Expiration The timeout attribute on the configuration element controls the expiration of the forms authentication ticket. However, in the case of session based cookies the Expires property of the cookie created by forms authentication is never set. Furthermore, with the cookieless support in ASP.NET 2.0 and ASP.NET 3.5, there may not even be a cookie created for the forms authentication ticket. Forms authentication computes the expiration time for a forms authentication ticket by adding the value of the timeout attribute to DateTime.Now. This value is passed as one of the parameters to the FormsAuthenticationTicket constructor. After a FormsAuthenticationTicket is created, it is converted to a hex-string representation using some custom internal serialization logic. This means the expiration date is packaged within the custom serialized representation of the ticket, regardless of whether the ticket is subsequently issued as a cookie or is instead placed on the URL for the cookieless case. Each time a forms authentication ticket arrives back at the web server, FormsAuthenticationModule opens either the cookie or the cookieless value on the URL, and converts the enclosed hex-string to an instance of FormsAuthenticationTicket. With a fully inflated ticket, the module checks the Expiration property to determine whether the ticket is still valid. This means that when a ticket is carried inside a cookie, FormsAuthenticationModule ignores any implied statement about expiration. Technically, if a cookie is sent to the web server, the browser agent that sent the cookie must consider the cookie still to be valid, meaning that the cookie has not expired yet. However, from a security perspective, it is trivial for a malicious user to generate a cookie and send it to the web server. As a result, forms authentication never depends on the expiration mechanism supported by HTTP cookies. It always consults the expiration date contained within the serialized ticket when determining whether the ticket is valid. If a cookie arrives at the web server, but the expiration date contained within the serialized ticket indicates that the ticket has expired, FormsAuthenticationModule recognizes this and doesn’t create a FormsIdentity based on the ticket. Furthermore, it removes the expired cookie from the Request.Cookies collection to prevent any downstream logic from making incorrect decisions based on the presence of the expired ticket. This approach also has the side benefit of forms authentication performing date comparisons based on the web server’s time. Although clock-skew probably exists between the current time on the web server and the current time on a client’s machine, as long as the cookie gets sent to the web server, the expiration date comparison is made using the server’s time. One question that arises from time to time is whether the expiration date of the ticket is maintained in Universal Coordinate Time (UTC). Unfortunately, when forms authentication was first implemented, it used the local date-time representation for the expiration date. Back in ASP.NET 2.0, the team considered changing this behavior through a configuration setting, but ultimately decided against it due to the following problems: ❑❑ Changing to a UTC-based expiration would break authentication in mixed ASP.NET 1.1 and ASP.NET 2.0 and ASP.NET 3.5 environments. The ASP.NET 1.1 servers would think the expiration date was in local time, when in reality the time was offset by many hours from the local time (assuming that your web server wasn’t sitting in the GMT time zone, of course!). Download at Boykma.Com 291 Chapter 6: Forms Authentication ❑❑ Although a configuration switch for ASP.NET 2.0 and ASP.NET 3.5 was a possibility, this would introduce a fair amount of confusion around when to turn it on or off. If the UTC time handling was turned on, and then later an ASP.NET 1.1 application was introduced into your web farm, ASP.NET 2.0 and ASP.NET 3.5 would have to be switched back to the original behavior. In two scenarios, local times potentially introduce problems for computing expiration times. ❑❑ In the United States, twice during the year, clocks are reset forward or backward by one hour. When a forms authentication ticket that was issued before the clock reset is sent back to the web server, the forms authentication feature incorrectly interprets the local time in that ticket. This means that one of two things happens: an extra hour is added to the ticket’s expiration, or one hour is subtracted from the ticket’s expiration. However, because this occurs at 1 AM local time (for the United States time adjustments), there probably is not a lot of traffic on your website that will encounter this oddity. ❑❑ If a website user browses across servers located in different physical time zones, and if the servers in each time zone are not set to use the same time zone internally, servers will incorrectly interpret the expiration date. For example, if a website load balances some of its users across servers on the West Coast and the East Coast of the United States, there is a three-hour time difference between the two coasts. If a forms authentication ticket is initially issued on the West coast at 10 AM local time, when the ticket is sent to a server on the East Coast, that server is going to compare the 10AM issuance against the fact that it is now 1 PM. This kind of discrepancy can lead to a user being forced to log in again. Because of these potential discrepancies, developers should be aware of the limitations of the local date time value stored in the forms authentication ticket. In the case of the clocks being reset twice a year, the current behavior is likely limited only to a few night owls. However, if your websites use geographic load balancing, keep in mind the forms authentication behavior. You could ensure that when a user has accessed a server in one geographic region, the user is routed back to the same geographic region on all subsequent requests. Alternatively, you could have a standard time zone that all servers use regardless of the time zone for the physical region that the servers are deployed in. On the other hand, if all of your geographically dispersed servers lie in the same time zone (maybe you have servers in New York City and others in Miami), you will not run into the forms authentication expiration issue. Working with the DateTime Issue with Clock Resets You do not need to read this section unless you are really, really curious about what happens when the server clock is reset! After struggling with this problem during the ASP.NET 2.0 design cycle, I figured I would share the code snippets and results. The following code is for a simple console application that simulates the problem with date time comparisons when the clock resets. C# static void Main(string[] args) { DateTime dtNow = DateTime.Now; 292 Download at Boykma.Com Chapter 6: Forms Authentication Console.WriteLine(“Use a 30 minute timeout just like forms authentication.”); Console.WriteLine(“The date value for now is: “ + dtNow.ToShortTimeString()); Console.WriteLine(“Has the time expired: “ + (dtNow.Add(new TimeSpan(0, 30, 0)) < DateTime.Now)); string breakHere = “Manually reset the clock “; DateTime dtNow2 = DateTime.Now; Console.WriteLine(“The date value for now after the clock reset is: “ + dtNow2.ToShortTimeString()); Console.WriteLine(“Has the time expired: “ + (dtNow.Add(new TimeSpan(0, 30, 0)) < DateTime.Now)); Console.ReadLine(); } VB.NET Shared Sub Main(ByVal args() As String) Dim dtNow As DateTime = DateTime.Now Console.WriteLine(“Use a 30 minute timeout just like forms authentication.”) Console.WriteLine(“The date value for now is: “ & dtNow.ToShortTimeString()) Console.WriteLine(“Has the time expired: “ & _ (dtNow.Add(New TimeSpan(0, 30, 0)) < DateTime.Now)) Dim breakHere As String = “Manually reset the clock “ Dim dtNow2 As DateTime = DateTime.Now Console.WriteLine(“The date value for now after the clock reset is: “ & _ dtNow2.ToShortTimeString()) Console.WriteLine(“Has the time expired: “ & _ (dtNow.Add(New TimeSpan(0, 30, 0)) < DateTime.Now)) Console.ReadLine() End Sub Running this inside of the debugger with a breakpoint in the dummy string assignment in the middle allows you to set the clock forward or backward prior to the next date comparison. The comparison against DateTime.Now is the same as the comparison that FormsAuthenticationTicket makes when you check the Expired property. Running the sample code and setting the clock back one hour during the breakpoint results in the following output: Use a 30 minute timeout just like forms authentication. The date value for now is: 10:27 AM Has the time expired: False The date value for now after the clock reset is: 9:27 AM Has the time expired: False The net result is that after the clock was set back one hour (just as is done during the last Sunday of October in most of the United States), an expiration time based on a 30-minute timeout will be valid until 10:57 AM. However, with the clock reset back to 9:27 AM, the lifetime of a ticket with this expiration is accidentally extended to 90 minutes. Download at Boykma.Com 293 Chapter 6: Forms Authentication Running the same code, but this time setting the clock forward one hour results in the following output: Use a 30 minute timeout just like forms authentication. The date value for now is: 10:33 AM Has the time expired: False The date value for now after the clock reset is: 11:33 AM Has the time expired: True Now the original expiration of 11:03 AM (10:33 AM issuance plus a 30-minute lifetime) is considered expired after the clock was set forward one hour (just as is done during the first Sunday in April). This occurs because after the clock is reset, the original expiration time of 11:03 AM (which is considered a local time) is compared against the newly updated local time of 11:33 AM and is considered to have immediately expired. The underlying technical reason for this similar behavior with forms authentication tickets is twofold: ❑❑ The serialization of the forms authentication ticket’s DateTime expiration uses a local time conversion (DateTime.ToFileTime and DateTime.FromFileTime). As a result, whenever a forms authentication ticket is deserialized on a web server, the .NET Framework hands back a DateTime instance that contains a local time value. ❑❑ The Expired property on FormsAuthenticationTicket is always compared against Date​ Time.Now. For the ticket to be UTC capable, you really need the ticket to be compared against DateTime.UtcNow. There is not an easy workaround to this whole issue. Aside from physical deployment steps, you can take to prevent part of the problem, the only ironclad way to ensure handling for all of these scenarios is for you to take over much of the management and verification of the forms authentication ticket, including the following: ❑❑ Manually construct the ticket and store the UTC expiration date inside of the UserData property of the FormsAuthenticationTicket. ❑❑ Manually issue the ticket. ❑❑ Hook a pipeline event prior to AuthenticateRequest (for example, BeginRequest), or hook the Authenticate event on the FormsAuthenticationModule directly. Then manually crack open and verify the ticket based on the UTC date previously stored in the UserData property of the FormsAuthenticationTicket. If you detect a discrepancy between the UTC-based comparison and the value of FormsAuthenticationTicket.Expired, you could force a redirect to reissue an updated cookie that contains an adjusted local time for the Expiration property. Whether this effort is worth it depends on the specific kind of application you are trying to secure. I suspect that for all but the most sensitive sites (for example, financial sites), the extra effort to deal with time mismatches that occur twice a year will probably not warrant the investment in time and effort. 294 Download at Boykma.Com Chapter 6: Forms Authentication Securing the Ticket on the Wire By default, the forms authentication ticket is digitally encrypted and signed using a keyed hash. This security has been available since ASP.NET 1.0, and ASP.NET 3.5 uses the same security for the ticket. However, there have been some new questions over hash security and support for new encryption options in ASP.NET 2.0 and ASP.NET 3.5. How Secure Are Signed Tickets? Since ASP.NET 1.0, forms authentication tickets have been digitally signed using a keyed hash that uses the SHA1 algorithm. When SHA1 was originally chosen years ago, it was considered a very secure hashing algorithm with no likelihood of being cryptographically weakened. In 2005, there were reports that SHA1 had been “broken”; in the cryptographic community, someone reported a theoretical collision-based attack on SHA1 hashes. In summary, some researchers proposed a way to reduce the chance of inducing a hash collision in SHA1 to only 269 attempts. Normally, you would expect to take around 280 attempts to create a collision in SHA1 (SHA1 hashes are 160 bits in length, so you can figure that on average you only need to flip half as many possible bits to eventually find a piece of text that results in a matching SHA1 hash.) So, this new attack against SHA1 theoretically reduces the number of attempts by a pretty hefty 1208335523804270469054464 iterations (after notepad, I think calc.exe is the most frequently entered command from the Run option in Windows). Suffice it say that that the current estimate of 269 attempts to find a SHA1 collision would still entail enormous computing resources. Depending on who you believe, it takes a few million years with commodity hardware or a few years with specialized cracking computers backed by the resources of the NSA. Regardless, it all boils down to the fact that “breaking” SHA1 is still incredibly difficult and time-consuming and realistically isn’t feasible with 2005-class hardware. However, in the cryptography community, weaknesses with hashing or encryption algorithms are like snowballs rolling down a steep hill. Weaknesses start out small, but as time passes and attacks are better understood, the combination of increased mathematical focus on these algorithms combined with ever-increasing computing power eventually leads to present-day algorithms being susceptible to viable attacks. Given the news about the SHA1 attack, there has been concern in the cryptography community around the long-term viability of SHA1 as a hashing algorithm. Some companies will probably start moving to SHA256 as a preemptive measure. There had been discussion on the ASP.NET team about whether one of the stronger SHA variants should have been added to (remember that defines the encryption and signing options for forms authentication, among other things). However, the team decided to stick with SHA1 because, technically speaking, forms authentication really uses HMACSHA1 (frequently referred to as a “keyed hash”), not just plain SHA1. In the case of , and thus forms authentication tickets, sticking with HMACSHA1 is a reasonable choice for the current ASP.NET 2.0 and ASP.NET 3.5 products. Download at Boykma.Com 295 Chapter 6: Forms Authentication The transient nature of nonpersistent forms authentication tickets means that in future framework releases, support for stronger SHA variants like SHA256 and SHA512 can be easily added. Such a change would impact applications that persistently store forms authentication tickets. Any application that truly needs security, though, should not be using persistent forms authentication tickets. The most likely future impact for developers would be around edge cases dependent on the total length of the characters in a forms authentication cookie. The stronger SHA variants contain more bits, and thus require more hex characters when converted to a string representation. This is normally more of a concern for cookieless tickets where ticket lengths are constrained. I cover issues with cookieless forms authentication tickets, including effective length restrictions, later in this chapter. Another reason for sticking with SHA1 as the hashing algorithm for forms authentication is that, as mentioned earlier, ASP.NET really uses HMACSHA1 (specifically the System.Security.Cryptography​ .HMACSHA1 class). This means that the value of the validationKey attribute in is used as part of the input to generate a SHA1 hash. As a result, for any attacker to force a hashing collision, not only does an attacker have to force a collision with the SHA1 result, an attacker also has to guess the key that was used with HMACSHA1. Just brute forcing SHA1 is not sufficient, because an attacker needs to know the validationKey that was provided as input to the HMACSHA1 algorithm. You can set the validationKey attribute of to a maximum length of 128 characters, which represents a 64-byte key value. The minimum allowable length for validationKey is 40 characters, which represents a 20-byte value. That means if you take advantage of the maximum allowable length, you have a 512 bit random value being used as the key, and an attacker has to somehow guess this value to create a viable hashing collision. I admit that I am definitely not a crypto-guru, so I can’t state how much stronger keying with HMACSHA1 is versus the plain SHA1 algorithm. However, with the added requirement of dealing with an unknown 512-bit key, the number of iterations necessary to force a collision with HMACSHA1 far exceeds either 269 or 280 iterations. One final note: developers may use a little-known method in the forms authentication API: Forms​ Authentication.HashPasswordForStoringInConfigFile. In ASP.NET 1.1, this was a convenient way to obtain a hex-string representation of a hashed password using MD5 or SHA1. Although originally intended for making it easier to securely populate the section contained within (since superseded by the more powerful and secure Membership feature in ASP.NET 2.0 and ASP.NET 3.5), customers have found this method handy as an easy-to-use interface to the hash algorithms. The problem today, though, is that with MD5’s strength in question, and now SHA1 potentially declining in strength, developers should really think about moving to SHA256 or SHA512 instead. However, the HashPasswordForStoringInConfigFile was not updated in ASP.NET 2.0 and ASP.NET 3.5 to support any of the other hash algorithms in the framework. Instead, you will need to write code to accomplish what this method used to do (and I strongly encourage moving to other hashing algorithms over time even though it will take a little more work). To make the transition a bit easier, the following console sample below shows how to perform the equivalent functionality but with the extra option of specifying the desired hashing algorithm. C# using System; using System.Security.Cryptography; using System.Collections.Generic; using System.Text; namespace HashPassword 296 Download at Boykma.Com Chapter 6: Forms Authentication { class Program { static void Main(string[] args) { if ((args.Length < 2) || (args.Length > 2)) { Console.WriteLine(“Usage: hashpassword password hashalgorithm”); return; } string password = args[0]; HashAlgorithm hashAlg = HashAlgorithm.Create(args[1]); //Make sure the hash algorithm actually exists if (hashAlg == null) { Console.WriteLine(“Invalid hash algorithm.”); return; } string result = HashThePassword(password, hashAlg); Console.WriteLine(“The hashed password is: “ + result); } private static string HashThePassword(string password, HashAlgorithm hashFunction) { if (password == null) throw new ArgumentNullException(“The password cannot be null.”); byte[] bpassword = Encoding.UTF8.GetBytes(password); byte[] hashedPassword = hashFunction.ComputeHash(bpassword); //Transform the byte array back into hex characters StringBuilder s = new StringBuilder(hashedPassword.Length * 2); foreach (byte b in hashedPassword) s.Append(b.ToString(“X2”)); return s.ToString(); } } } VB.NET Imports Microsoft.VisualBasic Imports System Imports System.Collections.Generic Imports System.Linq Imports System.Text Imports System.Web Imports System.Security.Cryptography Imports System.Security.Authentication Download at Boykma.Com 297 Chapter 6: Forms Authentication Namespace HashPassword Friend Class Program Shared Sub Main(ByVal args() As String) If (args.Length < 2) OrElse (args.Length > 2) Then Console.WriteLine(“Usage: hashpassword password hashalgorithm”) Return End If Dim password As String = args(0) Dim hashAlg As HashAlgorithm = HashAlgorithm.Create(args(1)) ‘Make sure the hash algorithm actually exists If hashAlg Is Nothing Then Console.WriteLine(“Invalid hash algorithm.”) Return End If Dim result As String = HashThePassword(password, hashAlg) Console.WriteLine(“The hashed password is: “ & result) End Sub Private Shared Function HashThePassword( _ ByVal password As String, _ ByVal hashFunction As HashAlgorithm) As String If password Is Nothing Then Throw New ArgumentNullException(“The password cannot be null.”) End If Dim bpassword() As Byte = Encoding.UTF8.GetBytes(password) Dim hashedPassword() As Byte = hashFunction.ComputeHash(bpassword) ‘Transform the byte array back into hex characters Dim s As New StringBuilder(hashedPassword.Length * 2) For Each b As Byte In hashedPassword s.Append(b.ToString(“X2”)) Next b Return s.ToString() End Function End Class End Namespace The main entry point performs a few validations, the important one being the confirmation of the hash algorithm. You can indicate the hash algorithm using any of the string representations defined in the documentation for the HashAlgorithm.Create method. As you would expect, you can use strings such as SHA1, SHA256, and SHA512. After the hash algorithm has been validated and created using the HashAlgorithm.Create method, the actual work is performed by the private HashThePassword method. The password is converted to a byte representation because the hash algorithms operate off of byte arrays rather than strings. Calling ComputeHash on the hash object results in the new hashed value. Because you are probably hashing these values with the intent of storing them somewhere and retrieving the values later, the hashed value is converted back into a string where two hex characters are used to represent each byte value. 298 Download at Boykma.Com Chapter 6: Forms Authentication I have included a few sample results from running this utility: C:\inetpub\wwwroot\379301_code\379301 ch06_code\cs\HashPassword\bin\Debug>Has hPassword pass!word MD5 The hashed password is: 0033A636A8B61F9EE199AE8FA8185F2C C:\inetpub\wwwroot\379301_code\379301 ch06_code\cs\HashPassword\bin\Debug>Has hPassword pass!word SHA1 The hashed password is: 24151F57F8F9C408380A00CC4427EADD4DDEBFC6 C:\inetpub\wwwroot\379301_code\379301 ch06_code\cs\HashPassword\bin\Debug>Has hPassword pass!word SHA256 The hashed password is: DE98DD461F166808461A3CA721C41200A7982B7EB12F32C57C62572C 6F2E5509 C:\inetpub\wwwroot\379301_code\379301 ch06_code\cs\HashPassword\bin\Debug>Has hPassword pass!word SHA512 The hashed password is: E84C057E3B6271ACC5EF6A8A81C55F2AB8506B7F464929417387BDC6 03E49BC0278DFAF063066A98EE074B15A956624B840DADBA65EDCF896521167C5DDE61CE As you would expect, the strong SHA variants result in substantially longer hash values. The simplicity of the sample code shows how easy it is to start using stronger hash algorithms in your code. Because the utility generates hashed values, you can validate user-entered passwords later with similar code; just convert a user-entered password into either the hex string representation or byte representation of the hash value, and compare it against the hash value that was previously generated with the sample code. Also note that the sample code uses unkeyed hash algorithms. As a result, you will get the same hash values for a given piece of input text regardless of the machine you use the utility on. This is because unkeyed hash algorithms apply the hash algorithm against the values you provide and do not inject any additional key material, as is done with an algorithm like HMACSHA1. Encryption Options in ASP.NET 2.0 and 3.5 In ASP.NET 1.0 and 1.1, you could encrypt the forms authentication ticket with either DES or 3DES. Normally, most developers use 3DES because DES has already been cracked. 3DES, however, is considered to be an old encryption algorithm as of 2005. In 2001, the National Institute of Standards and Technology (NIST) published the details for a new common encryption standard called the Advanced Encryption Standard (AES). AES is the replacement for 3DES, and over time most application developers and companies will shift away from 3DES and start using AES. ASP.NET 2.0 and ASP.NET 3.5 have support for AES so that developers can easily take advantage of the new encryption standard. AES has the benefit of supporting much longer keys than 3DES does. 3DES uses a 168-bit key (essentially three 56-bit keys), whereas AES supports key lengths of 128, 192, and 256 bits. To support the new encryption algorithm, ASP.NET 2.0 and ASP.NET 3.5 have a new configuration attribute in the section: By default, the decryption attribute of is set to Auto. In this case, ASP.NET 2.0 and ASP.NET 3.5 will look at the value in the decryptionKey attribute of to determine the appropriate encryption algorithm. If a 16-character value is used for decryptionKey, ASP.NET 2.0 Download at Boykma.Com 299 Chapter 6: Forms Authentication and ASP.NET 3.5 choose DES as the encryption algorithm (16 hex characters equate to an 8-byte value, which is the number of bytes needed for a DES key). If a longer string of characters is set in decryption​ Key, ASP.NET 2.0 and ASP.NET 3.5 choose AES. In the .NET Framework, if you look for a class called “AES” or “Advanced Encryption Standard” you will not find one. Instead, there is a class in the System.Security.Cryptography namespace called RijndaelManaged. Because the AES encryption standard uses the Rijndael encryption algorithm, ASP.NET used the RijndealManaged class when you choose AES. If an application’s decryptionKey attribute is at the default setting of Autogenerate, IsolateApps, ASP.NET will automatically use the randomly generated 24-byte (192-bit) value that was created for the current process or application identity. This also results in ASP.NET automatically selecting AES as the encryption option. You can see from this the symmetry in byte sizes for keys between 3DES and AES. In 3DES, the three 56-bit keys need to be packaged into three 64-bit values (8 bits in each value are unused as key material by 3DES), which works out to a 192-bit value. The same auto-generated key can be used with AES because AES supports 192-bit key lengths as well. If you choose to explicitly specify a value for decryptionKey (and I would highly recommend this because explicit keys are consistent values that you can depend on), you should ensure that the text value you enter in the section is one of those shown in the following table. Desired AES Key Length in Bits 128 192 256 Number of Hex Characters Required for decryptionKey 32 48 64 If you are working on anything other than a hobby or personal website, always do the following with : 1. Explicitly set the decryptionKey and validationKey attributes. Avoid using the auto- generated options. 2. Explicitly set the new decryption attribute to the desired encryption algorithm. Choose either 3DES for backward compatibility (more on this later) or AES. 3. Explicitly set the validation attribute. Choose SHA1, 3DES, or AES (remember that this setting is overloaded for viewstate encryption handling, hence the oddity of 3DES or AES specified for a validation algorithm). MD5 is not recommended because it isn’t as strong as SHA1. And of course, just to add to the confusion, choosing SHA1 here really means that forms authentication uses the keyed version: HMACSHA1. Depending on the auto-generated keys is fraught with peril. For a personal site or a hobbyist site that lives on a single machine, the auto-generated keys are convenient and easy to use. However, any website that needs to run on more than two machines has to use explicit keys because auto-generated keys, by definition, vary from machine to machine. 300 Download at Boykma.Com Chapter 6: Forms Authentication There is another subtle reason why you should avoid auto-generated keys. Each time you run aspnet_​ regiis with the ga option for different user accounts, the next time ASP.NET starts up in a worker process that uses these new credentials, a new set of auto-generated keys is generated! This means if you persistently store any encrypted information (maybe persisted forms authentication tickets, for example) that depends on stable values for the key material, you are only one command-line invocation of aspnet_​ regiis away from accidentally changing the key material. Also, when you upgrade an ASP.NET 1.1 site to ASP.NET 3.5, the auto-generated keys have all been regenerated with new values. I cover the implications of this in the section about upgrade implications from ASP.NET 1.1 to 3.5. Generating Keys Programmatically Encouraging developers to use explicit keys is not very useful if there is not a way to generate the necessary keys in the first place. Following is a simple console application that outputs the hex representation of a cryptographically strong random key given the number of desired hex characters. If you create similar code on your machine, make sure that the project includes System.Security in the project references. C# using System; using System.Security.Cryptography; using System.Collections.Generic; using System.Text; namespace GenKeys { class Program { static void Main(string[] args) { if ((args.Length == 0) || (args.Length > 1)) { Console.WriteLine(“Usage: genkeys numcharacters”); return; } int numHexCharacters; if (!Int32.TryParse(args[0], out numHexCharacters)) { Console.WriteLine(“Usage: genkeys numcharacters”); return; } if ((numHexCharacters % 2) != 0) { Console.WriteLine(“The number of characters must be a multiple of 2.”); return; } //Two hex characters are needed to represent one byte byte[] keyValue = new byte[numHexCharacters / 2]; //Use the crypto support in the framework to generate the random value RNGCryptoServiceProvider r = new RNGCryptoServiceProvider(); r.GetNonZeroBytes(keyValue); Download at Boykma.Com 301 Chapter 6: Forms Authentication //Transform the random byte values back into hex characters StringBuilder s = new StringBuilder(numHexCharacters); foreach (byte b in keyValue) s.Append(b.ToString(“X2”)); Console.WriteLine(“Key value: “ + s.ToString()); } } } VB.NET Imports Microsoft.VisualBasic Imports System Imports System.Collections.Generic Imports System.Linq Imports System.Text Imports System.Web Imports System.Security.Authentication Imports System.Security.Cryptography Namespace GenKeys Friend Class Program Shared Sub Main(ByVal args() As String) If (args.Length = 0) OrElse (args.Length > 1) Then Console.WriteLine(“Usage: genkeys numcharacters”) Return End If Dim numHexCharacters As Integer If (Not Int32.TryParse(args(0), numHexCharacters)) Then Console.WriteLine(“Usage: genkeys numcharacters”) Return End If If (numHexCharacters Mod 2) <> 0 Then Console.WriteLine(“The number of characters must be a multiple of 2.”) Return End If ‘Two hex characters are needed to represent one byte Dim keyValue(numHexCharacters \ 2 - 1) As Byte ‘Use the crypto support in the framework to generate the random value Dim r As New RNGCryptoServiceProvider() r.GetNonZeroBytes(keyValue) ‘Transform the random byte values back into hex characters Dim s As New StringBuilder(numHexCharacters) For Each b As Byte In keyValue s.Append(b.ToString(“X2”)) Next b Console.WriteLine(“Key value: “ & s.ToString()) End Sub End Class End Namespace 302 Download at Boykma.Com Chapter 6: Forms Authentication After some basic validations, the program determines the number of bytes needed based on the requested number of hexadecimal characters: because it takes two hex characters to represent a single byte value, you simply divide the command line parameter by two. To create the actual random value, call the RNGCryptoServiceProvider class in the System.Security.Cryptography namespace. In this example, I requested that the result not include any byte values of zero. Converting the byte array back into a hex string is also pretty trivial. The code simply iterates through the byte array of random values, converting each byte into its string equivalent. The “X2” string format indicates that each byte value should be converted to hexadecimal format, and that an extra “0” character should be included where necessary to ensure that each byte is represented by exactly two characters. If you do not do this, byte values from zero to fifteen require only a single hex character. The following example of using the tool is generating a 64-character (256-bit) value suitable for use with the AES encryption option. C:\inetpub\wwwroot\379301_code\379301 ch06_code\cs\\GenKeys\bin\Debug>genkeys 64 Key value: C5D08A900770821F2AC4CFA3727B28F68C8A8A1BC7A857BE6E588210051C0968 Setting Cookie-Specific Security Options Most developers probably use forms authentication in cookie mode. In fact, unless you happened to use the Microsoft Mobile Internet Toolkit (MMIT) in ASP.NET 1.1, ASP.NET could not automatically issue and manage tickets in a cookieless format. In ASP.NET 1.1 the requireSSL attribute on the element enabled developers to require SSL when handling forms authentication tickets carried in a cookie. The slidingExpiration attribute on allowed you to enforce whether forms authentication tickets would be automatically renewed as long as a website user stayed active on the site. In addition to these options, ASP.NET 2.0 and ASP.NET 3.5 include a security feature for the forms authentication ticket by always setting the HttpOnly property on the cookie to true. requireSSL The HttpCookie class has a property called Secure. When this property is set to true, it includes the string secure in the Set-Cookie command that is sent back to the browser. Browsers that recognize and honor this cookie setting send the cookie back to the web server only if the connection is secured with SSL. For any high-security site, the requireSSL attrbitue should always be set to true to maximize the likelihood that the cookie is only communicated over a secure connection. However, depending on client-side behavior is always problematic. The browser may not support secure cookies (unlikely but still possible with older browsers). Additionally, not every user on a website is a person sitting in a chair using a browser. You may have users that are really programs making HTTP calls to your site, in which case it is highly likely that such programs do not bother looking at or honoring any of the extended cookie settings like the secure attribute. In these cases, it becomes possible for the forms authentication cookie to be sent back to the web server over an insecure connection. Download at Boykma.Com 303 Chapter 6: Forms Authentication The forms authentication feature protects against this by explicitly checking the state of the connection before it starts processing a forms authentication cookie. If the FormsAuthenticationModule receives a valid cookie (meaning, the cookie decrypts successfully, the signature is valid, and the cookie has not expired yet), the module ignores it and clears the cookie from the Request collection if the requireSSL attribute in the configuration section was set to true and ASP.NET detects that the connection is not secure. From a user perspective, the cookie will not be used to create a FormsIdentity, and as a result no authenticated identity is set on the context’s User property. As a result, the user will be redirected to the login page. Programmatically, the check is easy to do and looks similar to the following: C# if (FormsAuthentication.RequireSSL && (!Request.IsSecureConnection)) VB.NET If FormsAuthentication.RequireSSL AndAlso ((Not Request.IsSecureConnection)) Then Both the requireSSL setting and the secured state of the current HTTP connection are available from public APIs. As a quick example, you can configure an application to use forms authentication but not require an SSL connection, as shown here: Run the application and login so that a valid forms authentication ticket is issued. Then change the configuration for to require SSL: Now when you refresh the page in your browser, you are redirected to the login page. If you attempt to log in again, the FormsAuthentication class will throw an HttpException when the code attempts to issue a ticket. For example, with code like the following: C# FormsAuthentication.RedirectFromLoginPage(“testuser”, false); VB.NET FormsAuthentication.RedirectFromLoginPage(“testuser”, False) You encounter the HttpException if you attempt this when the connection is insecure. Although you would probably think this is unlikely to occur (if you set requireSSL to true in configuration, you probably have SSL on your site), it is possible to run into this behavior when testing or developing an 304 Download at Boykma.Com Chapter 6: Forms Authentication application in an environment that does not have SSL. Because returning unhandled exceptions to the browser is a bad thing, you should defensively code for this scenario with something like the following: C# protected void Button1_Click(object sender, EventArgs e) { if (FormsAuthentication.RequireSSL && (!Request.IsSecureConnection)) { lblErrorText.Text = “You can only login over an SSL connection.”; txtPassword.Text = String.Empty; txtUsername.Text = String.Empty; return; } else { //Authenticate the credentials here and then … FormsAuthentication.RedirectFromLoginPage(txtUsername.Text, false); } } VB.NET Protected Sub Button1_Click( _ ByVal sender As Object, _ ByVal e As EventArgs) Handles Button1.Click If FormsAuthentication.RequireSSL AndAlso _ ((Not Request.IsSecureConnection)) Then lblErrorText.Text = “You can only login over an SSL connection.” txtPassword.Text = String.Empty txtUsername.Text = String.Empty Return Else ‘Authenticate the credentials here FormsAuthentication.RedirectFromLoginPage(txtUsername.Text, False) End If The check for the security setting and the current connection security duplicate the similar check that is made internally in a number of places in forms authentication. However, by explicitly checking for this, you avoid the problem of the forms authentication feature throwing any unexpected exceptions. It also gives you the chance to tell the browsers users to use an HTTPS connection to log in. This type of check should be used when calling any forms authentication APIs that may issue cookies such as RedirectFromLoginPage, and SetAuthCookie. The requireSSL attribute applies mainly to forms authentication tickets issued in cookies. If an application uses cookieless tickets, or if it has the potential to issue a mixture of cookie-based and cookie­ less tickets, it is possible to send cookieless tickets over a non-SSL connection. Although ASP.NET still disallows you from issuing cookieless tickets over insecure connections, ASP.NET accepts and processes cookieless tickets received over non-SSL connections. Keep this behavior in mind if you set requireSSL to true and still support cookieless tickets. Download at Boykma.Com 305 Chapter 6: Forms Authentication HttpOnly Cookies HttpOnly cookies are a Microsoft-specific security extension for reducing the likelihood of obtaining cookies through client script. In ASP.NET, the System.Web.HttpCookie class adds the HttpOnly property. If you create a cookie and set this property to true, ASP.NET includes the HttpOnly string in the Set-Cookie header returned to the browser. This is a Microsoft-specific extension to the cookie header. I am only aware of it being supported on IE6 SP1 or higher, although there are discussions on the Internet about building in support for it on other browsers, and most recently Firefox 2.0.0.5 has added the HttpOnly checking. Most other browsers just ignore the HttpOnly option in the cookie header, so setting HttpOnly for a cookie is usually innocuous. In some cases, however, browsers will drop a cookie with the HttpOnly option (for example, Internet Explorer 5). ASP.NET’s cookie writing logic will not emit the HttpOnly option for these cases. Technically, the way HttpOnly cookies work is that if a piece of client-side script attempts to retrieve the cookie, Internet Explorer honors the HttpOnly setting and will not return a cookie object. ASP.NET 3.5 enforces HttpOnly cookies all the time for forms authentication, as was the case in ASP.NET 2.0. This means that all forms authentication tickets contained in cookies issued by the FormsAuthentication API (for example, RedirectFromLoginPage and SetAuthCookie) will always have the HttpOnly setting appended to them. There was a fair amount of discussion about this internally because the change has the potential to be a pain for some customers. However, given the fact that many developers are not aware of the HttpOnly option (its original introduction was buried somewhere in IE6 SP1) having a configuration option to change this behavior did not seem like a great idea. If few people know about a certain capability, adding a configuration option to turn the capability on doesn’t really do anything to get the word out about it. Of course, ASP.NET 2.0 and ASP.NET 3.5 could still have added support for HttpOnly cookies by defaulting to turning the behavior on and then exposing a configuration setting to turn it back off again. The counterpoint to this option is that doing so gives developers a really easy way to open themselves up to cross-site scripting attacks that harvest and hijack client-side cookies. The reality is that if developers need a way to grab the forms authentication cookie client-side, the forms authentication APIs can still be pretty easily used to manually create the necessary cookie, but without the HttpOnly option turned on. Lest folks think that the pain around the decision to enforce HttpOnly for forms authentication tickets is limited to the developer community at large, the ASP.NET team has actually pushed back a number of times when internal groups asked for HttpOnly to be turned off. Repeatedly, the ASP.NET team has seen that architectures that depend on retrieving the forms authentication ticket client-side are flawed from a security perspective. If you really need the forms authentication ticket to be available from a client application, using the browser’s cookie cache as a surrogate storage mechanism is a bad idea. In fact, scenarios that require passing a forms authentication ticket around on the client-side frequently also depend on the need for persistent tickets (if the ticket were session-based, there would be no guarantee that the cookie would still be around for some other client application). So, now you start going down the road of persistent cookies that are retrievable with a few lines of basic JavaScript, which is not a big deal for low-security sites, but definitely something to avoid in any site that cares about security. 306 Download at Boykma.Com Chapter 6: Forms Authentication To see how the behavior affects forms authentication in ASP.NET 3.5, you can write client-side JavaScript like the sample shown here. You were logged in!
If you run this code on an ASP.NET 1.1 site that requires forms authentication, you get a dialog box that conveniently displays your credentials such as the one shown in Figure 6-1: Figure 6-1 If you run, same client-side script in an ASP.NET 3.5 application after logging in, you will not get anything back. Figure 6-2 shows the results on ASP.NET 3.5, which resembles that of ASP.NET 2.0. Figure 6-2 As mentioned earlier, if you really need client-side access to the forms authentication cookie, you need to manually issue the cookie and to manage reissuance of the authentication cookie in case you want to support sliding expirations. (With sliding expirations, FormsAuthenticationModule may reissue the cookie on your behalf.) Download at Boykma.Com 307 Chapter 6: Forms Authentication Although HttpOnly cookies make it much harder to obtain cookies through a client-side attack, it is still possible to trick a web server into sending back a page (including cookies) in a way that bypasses the protections within Internet Explorer. There are a number of discussions on the Internet about using the TRACE/TRACK command to carry out what is called a cross-site tracing attack. In essence, these commands tell a web server to send a dump of a web request back to the browser, and with sufficient clientside code, you can parse this information and extract the forms authentication cookie. Luckily, this loophole can be closed by explicitly disabling the TRACE/TRACK command on your web servers and/or firewalls. slidingExpiration You may not think of the sliding expiration feature as much of a security feature, but this setting does have a large effect on the length of time that a forms authentication cookie is considered valid. By default, in ASP.NET 2.0 and ASP.NET 3.5 sliding expiration is enabled (the slidingExpiration attribute is set to true in ). As long a website user sends a valid forms authentication cookie back to the web server before the ticket expires (30-minute expiration by default), the FormsAuthenticationModule periodically refreshes the expiration date of the cookie. The FormsAuthentication.RenewTicket​ IfOld method is used to create an updated ticket if more than 50 percent of the ticket’s lifetime has elapsed. The security issue is that with sliding expirations a website user could potentially remain logged on to a site forever. Even with the 30 minute default, as long as something or someone sends a valid ticket back to the server every 29 minutes and 59 seconds, the ticket will continue to be valid. On private computers or computers that are not in public areas, this really is not an issue. However, for computers in public areas like kiosks or public libraries, if a user logs into a site and does not logout, the potential exists for anyone to come along and reuse the original login session. You can’t control the behavior of your customers. (Even with a logout button on a website, only a small percentage of users actually use it.) You do, however, have the option to disable sliding expirations. When slidingExpiration is set to false, regardless of how active a user is on the website, when the expiration interval passes, the forms authentication ticket is considered invalid and the website user is forced to log in again. Of course, this leads to the problem of determining an appropriate value for the timeout attribute. Setting this to an excessively low interval annoys users, whereas setting it to a long interval leaves a larger window of opportunity for someone’s forms authentication ticket to be reused. Using Cookieless Forms Authentication ASP.NET 2.0 and ASP.NET 3.5 automatically support issuing and managing forms authentication tickets in a cookieless manner. The process starts by ASP.NET inspecting the request URL, looking for any cookieless tickets. In ASP.NET 2.0 and 3.5, cookieless tickets are supported for session state (this was also available in 1.1), forms authentication (previously available as part of the mobile support in ASP. NET), and anonymous identification (introduced since ASP.NET 2.0). A sample URL with a cookieless session state ticket is shown here: http://localhost/inproc/(S(tuucni55xfzj2xqx1mnqdg55))/Default.aspx 308 Download at Boykma.Com Chapter 6: Forms Authentication ASP.NET reserves the path segment immediately after the application’s virtual root as the location on the URL where cookieless tickets are stored. In this example, the application was called inproc, so the next path segment is where ASP.NET stored the cookieless tickets. All cookieless tickets are stored within an outer pair of parentheses. Within these, there can be a number of cookieless tickets, each starting with a single letter indicating the feature that consumes the ticket, followed by a pair of parentheses that contain the cookieless ticket. Currently, the following three identifiers are used: ❑❑ S: Cookieless ticket for session state ❑❑ A: Cookieless ticket for anonymous identification ❑❑ F: Cookieless ticket for forms authentication At some stage during the request life cycle, ASP.NET removes the cookieless tickets from the URL and inserts a new custom HTTP header to the current HTTP request called ASPFILTERSESSIONID that contains all the cookieless tickets that were already found in the current HTTP request. ASP.NET 2.0 and ASP.NET 3.5 base themselves on the above mechanism to support cookieless representations of forms authentication tickets, as well as anonymous identifiers (this second piece of information is only used with the Profile feature). You can enable cookieless forms authentication simply by setting the new cookieless attribute to in the configuration section: – AG The following table lists the options for the cookieless attribute. Cookieless Attribute Value UseUri UseCookies AutoDetect UseDeviceProfile Descrption Always issues the forms authentication ticket so that it shows up as part of the URL. Cookies are never issued. Always issues the forms authentication ticket in a cookie. Detects whether the browser supports cookies through various heuristics. If the browser does not appear to support cookies, issues the ticket on the URL instead. Finds a device profile for the current browser agent, and based upon the information in the profile, uses cookies if the profile indicates they are supported. This is the default setting in ASP.NET 2.0 and ASP.NET 3.5. Information for the device profiles is stored in the Browsers subdirectory of the framework’s CONFIG directory. ASP.NET ships with a set of browser information, including cookie support, for widely used browsers. You can edit the files in this directory, or add additional setting files, and then make the changes take effect with the aspnet_regbrowsers.exe tool. The default setting for the cookieless attribute is UseDeviceProfile. This means that your site will issue a mixture of cookie-based and URL-based forms authentication tickets, depending on the type of browser agent accessing your website. If you do not want to deal with some of the edge cases that occur when using cookieless tickets, you should set the cookieless attribute to UseCookies. Download at Boykma.Com 309 Chapter 6: Forms Authentication The nice thing about cookieless support in ASP.NET 2.0 and ASP.NET 3.5 is that other than changing a single configuration attribute, forms authentication continues to work. As a very basic example, issuing a cookieless forms authentication ticket on a login page with the familiar FormsAuthentication​ .RedirectFromLoginPage method results in a URL that looks something like the following (the URL is wrapped because the cookieless representation bloats the URL size): http://localhost/cookieless/(F(DJflxUBV0oD-JNW_FmuLwsvIEzBTYRk19QYcPG7gT95lkplFeRFwI-KxSdBIjDpzvSYGi5VQ8GY1PA2h9m6l4LwPa60gQ91nYGly9Bo79c1))/Default.aspx The bold portion of the URL is, of course, the forms authentication ticket. As mentioned above, ASP.NET takes care of removing the cookieless tickets from the URL and stores them into a custom HTTP header so that the ASP.NET engine can later on make use of the these tickets. Internally, cookieless features such as forms authentication rely on internal helper classes to move data from the custom HTTP header into feature specific classes, such as FormsAuthenticationTicket. If you dump the HTTP headers for the page in the previous URL, you will see the end result of the work performed by the ASP.NET runtime: HTTP_ASPFILTERSESSIONID=F(DJflxUBV0oD-JNW_FmuLwsvIEzBTYRk19QYcPG7gT9-5lkplFeRFwI-Kx SdBIjDpzvSYGi5VQ8GY1PA2h9m6l4LwPa60gQ91nYGly9Bo79c1) Unfortunately, in ASP.NET 2.0 and ASP.NET 3.5, the general-purpose class used internally for parsing the cookieless headers is not available as a public API. So, unlike the HttpCookie class, which gives developers the flexibility to create their own custom cookie-based mechanisms, cookieless data in ASP.NET 2.0 and ASP.NET 3.5 is supported only for the few features like forms authentication that have baked the support into their APIs. Cookieless Options You have seen the various cookie options that you can set on the cookieless attributes. Of the four options, UseCookies and UseUri are self-explanatory. However, I want to drill in a bit more on the other two options: AutoDetect and UseDeviceProfile. AutoDetect The AutoDetect option comes into play when forms authentication needs to determine whether a forms authentication ticket should be placed on the URL. ASP.NET 2.0 and ASP.NET 3.5 will go through several checks to see whether the browser supports cookies. Although going through this evaluation means that the initial ticket issuance takes a little longer, it does mean that for each and every new user on your website, you have a very high likelihood of being able to issue the forms authentication ticket in a way that can be received by the user’s browser. If new browsers are introduced, and the device profile information is not available yet on your server (an extremely common case in the mobile world where there seems to be a new device/browser/etc. every day), the AutoDetect option is very handy. When a browser first accesses a site, it is requesting one of three possible types of pages: ❑❑ Pages that allow anonymous users and, thus, do not require authentication. ❑❑ The forms authentication login page for the site. ❑❑ A secured page that requires some type of authenticated user. In this case, authorization will eventually fail and force a redirect back to the login page. 310 Download at Boykma.Com Chapter 6: Forms Authentication Phase 1 of Auto-Detection In the first case, forms authentication lies dormant and the auto-detect setting has no effect. After a browser accesses the types of pages indicated by the second and third bullet points, the Forms​ AuthenticationModule starts the process to detect whether or not the browser supports cookies. Depending on whether the browser is accessing the login page or a secured page, the internal path leading to auto-detection is a bit different. However, from a functionality perspective, the browser experiences the same behavior. The detection process goes through the following steps in sequence: 1. A check is made using the browser capabilities object available from Request.Browser. The information returned by this object is based on an extensive set of browser profiles stored on disk in the Browsers directory. If the browser capabilities definitively indicate that cookies are not supported, there is no additional detection needed. Short-circuiting the auto-detection process at this point saves time and unnecessary redirects. For classes of devices that simply do not support cookies, there isn’t any point in probing further in an attempt to send cookies. 2. If the browser capabilities for the current request indicate that cookies are supported, then a check is made to see if auto-detection occurred previously. If a previous browse path through the site already occurred, and if the results of that browsing indicated that cookies weren’t supported, the URL will already contain extra information indicating that this check occurred. Normally though, a user browses to the login page or a secured page for the first time, and thus auto-detection will not already have occurred. 3. A check is made to see if cookies have been sent with the request. For example, your site may have already issued some other kind of cookies previously when the user was browsing around. In this case, the mere presence of cookies sent back to the server is an indication that cookies are supported. 4. If all of the previous checks fail, ASP.NET adds some information to the current response. It adds a cookie to Response.Cookies called “AspxAutoDetectCookieSupport.” It also appends a query-string name-value pair to the current request path; the query-string variable is also called “AspxAutoDetectCookieSupport.” Because it is the only way to get this query-string variable onto the path in a way that the browser can replay it, a redirect to the currently requested page is then issued. The net result of this initial detection process is that for the nominal case of a browser first accessing the login page, or a secured page, a redirect to the login page always occurs. In the case that the user was attempting to directly access a secured page, the extra query-string and cookie information is just piggybacked onto the redirect that normally occurs anyway. On the other hand, if the user navigated to the login page directly, then ASP.NET forces a redirect back to the login page in order to set the querystring variable. In the browser’s address bar, the result looks something like the following: http://bhaidar-pc/cookieless/login.aspx?AspxAutoDetectCookieSupport=1 At this point if the browser supports cookies, there is also a session cookie held in the browser’s cookie cache called “AspxAutoDetectCookieSupport.” So, there is potentially both a query-string variable and a cookie value client-side in the browser waiting to be sent back to the web server. Of course, on browsers that don’t support cookies, only they query-string variable will exist. Download at Boykma.Com 311 Chapter 6: Forms Authentication Phase 2 of Auto-Detection After the user types in credentials and submits that login page back to the server, the auto-detect steps listed earlier are evaluated again because the FormsAuthenticationModule always triggers these steps for the login page. However, because the auto-detection process already started, one of two decisions is made: ❑❑ If the browsers supports cookies, the auto-detect cookie will exist and the forms authentication feature will determine that cookies are supported. ❑❑ If the auto-detect cookie was not sent back by the browser, a check is made for the auto-detect query-string variable. Because this query-string variable now exists, ASP.NET will add a cookieless value to the URL that indicates the browser does not support cookies. A value of “X(1)” is inserted into the URL and will exist in all subsequent requests that the browser makes to the site for the duration of the browser session. Phase 3 of Auto Detection The code in the login page needs to process the credentials that were posted back to it at this point. If the credentials are invalid, then the browser remains on the login page, and Phase 2 will repeat itself when the user attempts another login. If the credentials are valid, though, then usually either Forms​ Authentication.RedirectFromLoginPage or FormsAuthentication.SetAuthCookie is called to create the forms authentication ticket and package it up to send back to the client. In the case that the browser supports cookies, the ticket is simply packaged into a cookie and added to the Response.Cookies collection. However, if the auto-detect process determined that cookies are not supported then both of these methods will package the hex string representation of the forms authentication ticket into the URL. The general form of the cookieless ticket in the URL is F(ticket value here). The sample address bar below shows the results of a successful login on a site that uses auto-detection. Note how both the “X” and the “F” identifiers exist in the URL: one indicating the cookies are not supported and the other containing the cookieless ticket. To make it bit easier to see everything, the X and F identifiers are bolded. http://bhaidar-pc/cookieless/(X(1)F(eKZT5gWUi7Le_4tXEei9Lgu2l1crhX23Kq-zdV6P8D8ActC VtgVWOsLMlvGiwEynmTbYLCzVWnj0n5MJtidRjf9kij0gkv-DL-1MxzqnlRU1))/Default.aspx Subsequent Authenticated Access After logging in, there really aren’t additional phases to the initial auto-detection process. Autodetection has occurred, and the results of the process are now indelibly stamped into the URL and maintained on each and every request. ASP.NET automatically takes care of hoisting the embedded URL values into the custom header using ASP.NET runtime, and various downstream components like forms authentication contain the necessary logic to check for cookieless artifacts (such as the X identifier and the F ticket in the URL). How to Simulate This in Internet Explorer It can be a bit of a pain to actually get auto-detection to slip into cookieless mode using a browser like Internet Explorer. By default, IE of course supports cookies, so setting “AutoDetect” in config will only show you the parts of the first two phases of auto-detection before defaulting to using cookies. 312 Download at Boykma.Com Chapter 6: Forms Authentication However, with a bit of rooting around inside of IE, you can force it to reject or prompt for cookies—at which point you have a way to simulate a cookieless browser. First, go to Tools ➪ Internet Options and click the Privacy tab. Clicking the Advanced button pops up another dialog box, as shown in Figure 6-3. In my case, I set the options for cookies to Prompt, though if you don’t want the hassle of always rejecting cookies you can just set the options to Block. Figure 6-3 Now you can navigate to your website to test it in cookieless mode. However, you must request your pages using the machine name of your web server. Looking at the last few URL samples, notice how the URL starts with a machine name (http://bhaidar-PC) as opposed to the usual http://localhost. If you use http://localhost, the cookie options you set on the Privacy tab are ignored. UseDeviceProfile Device profiles are another mechanism for determining browser cookie support. Although an exhaustive description of devices profiles is outside the scope of this book (the current browser profiles include reams of information that mobile developers care about but that aren’t terribly relevant to security or forms authentication), it is still important to understand where the profiles are located and, in general, how profile information affects detection of cookie support. UseDeviceProfile is the default setting of the cookieless attribute in forms authentication. This means that whenever the forms authentication feature needs to determine whether a browser supports cookies, it looks only at the values of Request.Browser.Cookies and Request.Browser.Supports​ RedirectWithCookie. If both those values return true, then forms authentication issues tickets in a cookie; otherwise, it uses the F() identifier in the URL. The information in the Browser property, which is an instance of System.Web.HttpBrowser​ Capabilities, comes from browser information files located at: %windir%\Microsoft.NET\Framework\v2.0.50727\CONFIG\Browsers Download at Boykma.Com 313 Chapter 6: Forms Authentication ASP.NET 3.5 Uses the .NET Framework 2.0 Installation Folder You should have noticed the Windows path that points to the Browsers folder inside the .NET Framework 2.0 installation folder. In fact, ASP.NET 3.5, in its core, depends on ASP.NET 2.0 and adds only a few new features. This is also shown when you configure a web application in IIS 7.0 and notice that its application pool points to the .NET 2.0 Framework and not .NET 3.5 Framework; hence, ASP.NET 3.5 uses the same engine as ASP.NET 2.0 and adds some new features. Note that the actual version number for the framework may be slightly different at release. This directory contains two dozen different files, all ending in .browser. ASP.NET internally parses the information in the .browser files, and based on the regular expression-based matching rules defined in these files, determines which .browser file applies based on the user agent string for a specific request. For example, when running Internet Explorer on my machine, the user agent string that IE sends down to the web server looks like this: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506; .NET CLR 3.5.21022) HTTP_UA_CPU:x86 If you look in the Browsers subdirectory, and open up the file ie.browser, you will see that the browser capabilities files define a regular expression matching rule like the following: Just from glancing at the regular expression syntax you can see how a match occurs, anchored around the Mozilla and MSIE identifiers in the user agent string. When ASP.NET evaluates this regular expression at runtime, and finds a match, it consults the other information in the ie.browser file and uses it for the information returned in Request.Browser. For example, if you were to query Request​ .Browser.TagWriter, you would get back the string System.Web.UI.HtmlTextWriter. I use the TagWriter property as an example because without the browser capabilities files, there is no way ASP.NET could possibly come up with a .NET Framework class name just from the information sent in the HTTP request headers. If you open up ie.browser in Notepad, and scroll down a bit to the section, you see a number of individual elements. The one of interest to forms authentication is: Because this capability is set to true, in the default out-of-box ASP.NET configuration, forms authentication will always assume that IE browsers support cookies. You can verify this behavior by doing the following: 1. Change the value in the capability to false and save the .browser file. 2. Recompile the browser capabilities assembly. You can do this by running the command aspnet_regbrowsers -I from the framework install directory. This has the effect of reparsing all of the .browser files and then encapsulating their settings inside of a GAC’d assembly. Note that if you fail to do this the changes made in step 1 will not have any effect. 314 Download at Boykma.Com Chapter 6: Forms Authentication 3. Within Internet Explorer, make sure you carried out the steps described earlier in the “How To Simulate This” section. 4. Set the cookieless attribute in web.config to UseDeviceProfile. Now if you request an authenticated page in the browser, forms authentication will use the device profile information, and thus automatically assume that the browser doesn’t support cookies. No autodetection mechanism is necessary. When you log in, forms authentication will place the forms authentication ticket in the URL inside of the F() characters. Unlike the auto-detect case, though, there will be no X(1) in the URL, because the device profile deterministically indicates that the browser does not support cookies. Although editing the IE device profile is a bit contrived, device profiles provide a fixed way for determining cookie support in a browser. The downside of UseDeviceProfile is that it can’t accommodate new browser types that have totally new user agent strings (for example, if I created a new browser that sent back a user agent string My New Browser, this isn’t going to match any of the predefined regular expressions defined in the various browser capabilities files). In this case, ASP.NET will simply fall back to the settings in the Default.browser file, which may or may not contain correct information. As a side note, Default.browser indicates that cookies are supported, so any user agent that is not recognized by the myriad .browser files shipping in ASP.NET 2.0, which are also shared with ASP.NET 3.5, will automatically be considered to support cookies. Another limitation of UseDeviceProfile is that device profiles don’t honor the intent of the browser user. A website user may intentionally disable cookies in any of the major desktop browsers. However, with UseDeviceProfile the user can never log in to your site because ASP.NET will always assume that cookies are supported. Each time the user attempts to log in, ASP.NET will send the forms authentication cookie back, and of course the browser will promptly reject it. Then when the browser redirects to a secured page, the lack of the cookie will simply dump the browser right back to the login page. Although you definitely have the option of telling website customers up front that cookies are required to log in, you also have the option of switching to AutoDetect instead. If you have a sizable percentage of customers that do not want to use cookies (or perhaps you have regulations that mandate support for cookieless clients), then the AutoDetect option may be a better choice than UseDeviceProfile. However, make sure to read the topic about security implications of cookieless tickets below so that you understand the ramifications of placing the authentication ticket in the URL. Replay Attacks with Cookieless Tickets Although both cookie-based and cookieless forms authentication tickets are susceptible to replay attacks, the ease with which a cookieless ticket can be disseminated makes it especially vulnerable. As an example of how easy it is to reuse a cookieless ticket, try the following sequence of steps on an ASP.NET site that is configured to run in cookieless mode. 1. Log in with valid credentials and confirm that the cookieless ticket shows up in the address bar of the browser. 2. Copy and paste the contents of the address bar into some other location like notepad. 3. Shut down the browser. Download at Boykma.Com 315 Chapter 6: Forms Authentication At this point, you have your very own forms authentication ticket sitting around and available for replay for as long as the expiration date inside of the authentication ticket remains valid. If you paste the URL back into a new instance of your browser, you will successfully navigate to the page indicated in the URL. If you know the names of other pages in the site, you can edit the pasted URL; the important and interesting piece of the URL is the forms authentication ticket embedded within it. Probably the most likely potential for security mischief with cookieless tickets in this case is not a malicious user or hacker. Rather, website users that don’t understand the ramifications of having the forms authentication ticket in the URL are the most likely candidates for accidentally inflicting a replay attack on themselves. Imagine the following scenario: 1. A website customer visits an e-commerce site that issues cookieless authentication tickets. The customer adds some items to a shopping cart and then logs in to start the checkout process. 2. At some part into the checkout process, the customer has a question—maybe about price. So, the customer copies the URL into an email message. Or for a nontechnical user, just selects File ➪ Send ➪ Link by Email. Now the customer has a URL with a valid forms authentication ticket sitting in an email message. 3. When the recipient receives the message, the recipient clicks the URL in the email (or the URL may be packaged as a clickable URL attachment), and surprise! The recipient just “logged in” to the e-commerce site as the original user. Given the default of sliding expirations in ASP.NET 2.0 and ASP.NET 3.5 forms authentication, after a cookieless ticket makes it outside of the boundaries of the browser session where the ticket was originally issued, it can be reused as long someone uses the ticket before the expiration period is exhausted. This scenario gives rise to a very specific piece of security guidance when using cookieless forms authentication: Never use sliding expirations when there is any chance of issuing cookieless tickets! I understand many of the arguments that can be made against this advice—chiefly that authentication tickets with absolute timeouts lead to a poor customer experience. However, I guarantee that if website customers accidentally email their forms authentication ticket, their ire over exposing their personal account will vastly exceed the pain of customers having to periodically log back in again. And don’t forget that after someone accidentally leaks his or her forms authentication ticket in an email, every server and network route along the delivery path has the potential of sniffing and stealing a perfectly valid cookieless ticket. Although the scenario I described earlier involves a customer sending a link to a secured page in a site, the reality is that after the forms authentication ticket is embedded on the URL, it remains there for the duration of the browser session. This means that if a customer logs in to start a checkout process but then clicks back to a publicly available page (maybe the customer clicks back out to an items detail page in a web catalog), the forms authentication ticket is still in the URL. I will grant you that sending an email link from deep inside a checkout process is probably unlikely. However, accidentally emailing the forms authentication credentials from a catalog page in an e-commerce site strikes me as a very likely occurrence. 316 Download at Boykma.Com Chapter 6: Forms Authentication This leads to a few additional pieces of advice about cookieless tickets: 1. Do not use cookieless tickets for any type of high-security site. For example, do not use cookie- less tickets for an online banking or investment site. The risk of someone accidentally compromising themselves far outweighs the convenience factor. 2. If you set the requireSSL attribute on your site to true, ask yourself why you are allowing cookieless tickets. The requireSSL attribute doesn’t protect cookieless tickets; it works only for cookie-based tickets. Although it is reasonable to set requireSSL to true on sites that support mixed clients (the theory being that at least the browsers that do support cookies will have a more secure experience), be aware that for cookieless users the forms authentication ticket can be issued and received over non-SSL connections. 3. Try to set the timeout attribute on sites that support cookieless clients to as small a value as pos- sible. I would not recommend setting a timeout greater than 60 minutes, although it is understandable if you can’t get much shorter than 45 minutes given the usage trends on e-commerce sites. 4. If you think your cookieless customer base will accept it, you should reauthenticate the cus- tomers prior to carrying out any sensitive transaction. This would mean requiring cookieless customers to reenter their username and password when they attempted to finalize a purchase or when they attempt to retrieve or update credit card information. The Cookieless Ticket and Other URLs in Pages Throughout the discussion, it has been stated that ASP.NET automatically handles maintaining the cookieless ticket in the URL. Although this is true for server-side code, the placement of the cookieless ticket in the URL also depends on browser behavior with relative URLs. If you look carefully at the sample URLs shown earlier, you can see that the URL consists of a few pieces. For a page like default.aspx, the browser considers the current path to the application to be: http://bhaidar-pc/cookieless/(X(1)F(eKZT5gWUi7Le_4tXEei9Lgu2l1crhX23Kq-zdV6P8D8ActC VtgVWOsLMlvGiwEynmTbYLCzVWnj0n5MJtidRjf9kij0gkv-DL-1MxzqnlRU1)) This means that the browser sees the cookieless information as part of the directory structure for the site. If you embed relative URLs into your page such as: Click me. I’m a regular A tag. Then whenever you click these types of links, the browser will prepend it with the current path information from the current page. So, this tag is interpreted by the browser as: http://bhaidar-pc/cookieless/(X(1)F(eKZT5gWUi7Le_4tXEei9Lgu2l1crhX23Kq-zdV6P8D8ActC VtgVWOsLMlvGiwEynmTbYLCzVWnj0n5MJtidRjf9kij0gkv-DL-1MxzqnlRU1))/SomeOtherPage.aspx On the other hand, if you embed absolute hrefs in your pages, you will lose the forms authentication ticket when someone clicks on the link. For example, if you accidentally created the tag as: Click me. I’m a regular A tag. Download at Boykma.Com 317 Chapter 6: Forms Authentication The address that your browser will navigate to is: http://bhaidar-pc/SomeOtherPage.aspx With this style of URL, you can see that the forms authentication ticket is lost. Now, for a simple application, you may not need to use absolute URLs. However, if you have a more complex navigation structure, perhaps with a common menu or navigation bar on your pages, you may very well have a set of fixed URLs that users can click. Unfortunately, cookieless forms authentication and absolute URLs do not mix, so you will need to write extra code to account for this behavior. Although a bit kludgy, an easy way to maintain a common set of URL endpoints like this is with a redirection page. Instead of the browser “knowing” the correct endpoint URL it should navigate to, you can convert these types of links into GET requests against a common redirection page. For example, you can use the LinkButton control to postback to ASP.NET: SomeOtherPage In the code-behind, the click event looks like: Response.Redirect(“/cookieless/SomeOtherPage.aspx”); Now when you click the link the browser, the page posts back to ASP.NET, and a server-side redirect is issued that retains in the all cookieless ticket information in the URL. The reason server-side redirects work is that Response.Redirect includes extra logic that ensures all of the information in the custom HTTP_ASPFILTERSESSIONID HTTP header is added back into the URL that is sent back to the browser. When the redirect reaches the browser, it has the full URL including the cookieless tickets. One last area where URL format matters is in any postback event references in the page. In fact, the LinkButton example depended on the correct behavior when posting the page back to itself. Because just about every ASP.NET control depends on postbacks, it would be pretty painful if postbacks did not correctly retain all cookieless tickets. ASP.NET is able to retain the cookieless tickets by explicitly embedding them in the “action” tag of the page’s
element. Taking the previous LinkButton example, if you view the source of the page in the browser, the form element looks like: Because much of the postback infrastructure depends on calling the JavaScript submit() method of a form, and the action attribute on the form includes the cookieless information, any attempt to programmatically submit a form (whether this is ASP.NET code or JavaScript code that you write) will include the cookieless information. Overall, ASP.NET will, for the most part, correctly retain the cookieless tickets in a transparent manner. Only if you embed absolute URLs in your pages, or if you use absolute URLs in your code-behind, will you lose the cookieless tickets. You should try to use relative URLs in page markup, and application-relative URLs in code-behind and for attributes of ASP.NET server controls. Although there are cases in server-side code where you can write code with URLs that are absolute virtual paths (that is, 318 Download at Boykma.Com Chapter 6: Forms Authentication /myapproot/somepage.aspx), depending on whether you use this style of URL with Response​ .Redirect versus in a control property, you will get different behavior. Coding with applicationrelative URLs (that is, ~/somepage.aspx) gives you consistent behavior with cookieless tickets regardless of where you use the application-relative URL. The following table shows various pieces of code and whether or not cookieless tickets are preserved. Code That Uses URLs Response.Redirect(“~/SomeOtherPage.aspx”); Response.Redirect(“SomeOtherPage.aspx”); Response.Redirect(“/cookieless/SomeOtherPage.aspx”); Response.Redirect(“http://bhaidar-PC/ cookieless/SomeOtherPage. aspx”); Are Tickets Retained? Yes Yes Yes No Yes No Yes No Payload Size with Cookieless Tickets When you support cookieless tickets with forms authentication, you need to be careful of the size of the forms authentication ticket in the URL. Although forms authentication in cookie mode technically also has issues with the size of the ticket, you have roughly 4K of data that you can work with in cookie mode. However, in cookieless mode, two factors work against you and limit the overall amount of data that you can place in a FormsAuthenticationTicket: ❑❑ There are other cookieless features in ASP.NET that also may place cookieless identifiers on the URL. Both session state and anonymous identification can take up space in the URL. ❑❑ On IIS 7, you cannot have more than 260 characters in any individual path segment (assuming you do not edit the configuration section in either the Application​ Host.config file or in the application’s web.config file). If you think about it, the 260-character constraint is actually pretty limiting and basically means that little more than username and expiration date can be effectively shipped around in a cookieless ticket. The previous sections on cookieless tickets regularly resulted in 100 or more characters being used on the URL for the ticket. Download at Boykma.Com 319 Chapter 6: Forms Authentication You can turn on anonymous identification and session state in web.config, and force them to run in cookieless mode with the following configuration settings, respectively (they use the same values for the cookieless attribute as forms authentication): Without even logging in to a sample application with these settings, the URL includes the following cookieless tickets (assume auto-detection is used for forms authentication for the absolute worst-case scenario). ( X(1) A(ABa15sfNyAEkAAAAOTVkYmQ4MjYtZjM0Zi00NmYxLWE4MTMtYjNkOGMzMDA2N2ZiDvoszMRsIAPx3LnxE 4OL-yxa0Bg1) S(vufwu245awzb32v21oyir245) ) Adding this all up, and ignoring the line breaks because those exist just for formatting in the book, there are: ❑❑ 2 characters for the beginning and closing parentheses ❑❑ 4 characters for the auto-detection marker “X” ❑❑ 95 characters for the anonymous identification ticket “A” ❑❑ 27 characters for the session state identifier “S” Without forms authentication even being involved, ASP.NET has already consumed 128 characters on the URL, which leaves a paltry 132 characters for forms authentication. The most obvious piece of information that drives variability in the size of the forms authentication ticket is the username. You may not realize it, but the value of the path configuration attribute could also contribute to the variable size of the ticket. By default, the path is set to /, so this only adds one additional character to the ticket prior to its encryption. In cookieless mode though, because the ticket is embedded in the URL, there isn’t really a concept of path information. As a result, in cookieless mode the path is always set to / by forms authentication, and hence there is always the same overhead in cookieless tickets for the path value. Other information such as a ticket version number and the issue and expiration date information are fixed size and don’t vary from one website to another. Logging in to a sample application with a comparatively short username (testuser) adds the following forms authentication ticket to the URL: F(hZ-dwIinilHGQZ76f7fHcvJqG3iJngm1M-wnx3w6DSmV8FZcQSF6p6GpBXSK85G4YHXRUOlfdzmtV7cUv doDGZu3mGiwmOMYBtcdB8RPVao1) This adds another whopping 111 characters to the URL. Now with all cookieless features enabled there are 239 characters consumed for the various cookieless representations. Playing around a bit with different usernames on the sample application, the longest username that worked was testuser​ 123456789012 (that is, a 20-character username). This results in an F ticket that is 132 characters long— resulting in a path segment that is 260 characters long. That is right on the 260 character path segment 320 Download at Boykma.Com Chapter 6: Forms Authentication limit enforced by the configuration section in the ApplicationHost​.config configuration file and the limitations set by the http.sys file that was introduced in Chapter 1. After the username increases to 21 characters, a 400 Bad Request error is returned. HTTP Error 404.14 – URL_TOO_LONG Going back to the path configuration attribute, you can explicitly set it to match the application’s root: Logging, with just testuser for the username results in a 111-character length for the forms authentication cookieless ticket (the same as before). And as before, the upper limit on the username is 20 characters. If you are curious what happened to the path information from configuration, the value of FormsAuthenticationTicket.CookiePath is hard-coded to /, regardless of the value in configuration. At one point earlier in the ASP.NET 2.0 development cycle, the full path value from configuration was included in cookieless tickets. Because this consumed far too much space on the URL (you could come up with a long enough path that even a zero-length username was too much to fit in the URL), the decision was made to always use the hard-coded / value. Keep this quirk in mind if for any reason you were depending on the FormsAuthenticationTicket.CookiePath property anywhere in your code; it should not be relied upon if your application ever issues cookieless forms authentication tickets. Of course, the size constraints on the URL are a bit more relaxed if you do not use other cookieless features. Turning off anonymous identification (because that is gobbling up 95 characters), a 40-character username results in around a 230-character URL. Because 40-character usernames are pretty unlikely, you have breathing room on the URL after anonymous identification is disabled. If you use cookieless forms authentication tickets, keep the following points in mind: ❑❑ With all cookieless features turned on, you are limited to a maximum length of around 20 characters for usernames with forms authentication. ❑❑ With anonymous identification turned off, you will probably not run into any real-world constraints on username length, unless of course you allow email addresses for usernames. Because email addresses can be upwards of 256 characters long, you will need to limit username length for such applications. One final point on how cookieless tickets are embedded in the URL: Even though ASP.NET 2.0 and ASP.NET 3.5 embed them all into a single path segment, future releases may choose to split out the cookieless tickets for various features into separate path segments. If this approach is ever taken, it would free up quite a bit more space for forms authentication, enough space that even UserData could store limited amounts of information. For this reason, I would recommend that developers avoid writing code that explicitly parses the URL format used by ASP.NET 2.0 and ASP.NET 3.5 or that depends on the specific layout of cookieless tickets. Continue to manipulate URLs with the built-in ASP.NET APIs and the application-relative path syntax. Writing code that has an explicit dependency on the ASP.NET 2.0 and ASP.NET 3.5 cookieless format may lead to the need to rework such code in future releases. Download at Boykma.Com 321 Chapter 6: Forms Authentication Unexpected Redirect Behavior Cookieless forms authentication introduces another subtle gotcha due to the reliance on redirects. The initial set of redirects that occur during autodetection does’t complicate matters because this logic runs as part of the normal redirection to a login page. In existing ASP.NET 1.1 applications, developers already have to deal with the possibility of a website user posting data back to a secured page, only to get redirected to the login page instead—along with the subsequent loss of any posted data. However, a bit of an edge case arises when using cookieless tickets, regardless of the selected cookieless mode. If you allow sliding expirations with cookieless tickets (and for security reasons this is not advised), then it is possible that at some point FormsAuthenticationModule may detect that more than 50% of a ticket’s lifetime has elapsed. The module always calls FormsAuthentication.RenewTicketIfOld on each request, for both cookied and cookieless modes. In the case of cookieless modes, though, if the module detects that a new forms authentication ticket was issued with an updated expiration time due to the renewal call, the module needs to ensure that the new ticket value is embedded on the URL. The module accomplishes this by repackaging the new FormsAuthenticationTicket into the custom HTTP_ASPFILTERSESSIONID header and then calling Response.Redirect , specifically the overload of Response.Redirect that accepts only the redirect path. This means the current request is immediately short-circuited to the EndRequest phase of the pipeline, and the redirect with the updated URL is sent back to the browser. From the user’s perspective, this means that anytime the user is working in the website (and this can be on a secured page or a publicly accessible page), enough of the ticket expiration may have elapsed to trigger a redirect. If by happenstance this redirect occurs when posting back user-entered data, the user is going to be one unhappy camper. Imagine entering a form full of registration data, hitting submit, and the net result is that you end up back on the same page with all of the fields showing as empty! You can simulate this behavior with a simple page that has a few text boxes for entering data. Add a button that posts the page back to the server. Set the timeout attribute in the configuration element to 2 minutes. Log in to the site, and navigate to the page with the text boxes. Type in some data, and then wait around 1.5 minutes, long enough for the ticket to need renewal. Now when you post back, you can see that all of the data you entered has been lost. This behavior is another reason why sliding expirations should be avoided when using cookieless tickets. About the only workaround (and an admittedly crude one at that) is for developers to identify pages in their site where user-entered information is not posted back in a form variable. For example, maybe viewing a catalog page in a website relies on query-string variables and a GET request, which allows the query-string variables to be preserved across redirects. You can write some code that runs in the pipeline (after FormsAuthenticationModule runs) and pro-actively checks the expiration date of the ticket. Rather than waiting for the ASP.NET default of 50% or more of the ticket lifetime to elapse, you could be more aggressive and force a ticket to be reissued at shorter intervals. This at least gives you some control over when the ticket is reissued, and it increases the likelihood that the ticket is reissued at well-defined points in the website where you can be assured that user-entered data is not lost. Of course, there are myriad side effects with this workaround: ❑❑ Redirection behavior is still hard to test. You have to laboriously test each page in the site where you may inject a proactive renewal of the forms authentication ticket. 322 Download at Boykma.Com Chapter 6: Forms Authentication ❑❑ The extra, and potentially unnecessary, redirects make the website seem slower. ❑❑ The workaround still doesn’t solve the problem of a user entering a checkout process, getting up from the computer, and coming back a little later after more than 50 percent of the lifetime for his or her current ticket has elapsed. This specific scenario is one where dumping the user back to the page they were just on, with empty fields, is likely to cause the user to bail out of the checkout process. Unfortunately, there isn’t an elegant solution to the unintended redirect problem with cookieless tickets. The best advice is to turn off sliding expirations, and set the forms authentication ticket lifetime to a “reasonable” value (say somewhere around 30 to 60 minutes). Configuring Forms Authentication Inside IIS 7.0 The configuration section is usually edited inside the application’s web.config. If you want to change the default predefined values, you configure a section by specifying the name, path, login page, cookieless mode, authentication cookie time-out, sliding expiration, whether forms authentication requires SSL, and whether the authentication cookie is enabled for cross-application redirects. Although the application’s web.config configuration file provides a very good IntelliSense to manipulate the different configuration sections, IIS 7.0, among the many new integration features with ASP.NET, provides a graphical user interface to edit the application’s authentication configuration section and some other configuration sections, too (SessionState is an example). To edit the authentication section, right-click on the FormsAuthenticationModule inside the Authentication applet window. Figure 6-4 shows the IIS 7.0 Windows Form used to edit the section for an application. As you can see, the entire authentication configuration section is now editable through the IIS 7.0 Manager tool. Figure 6-4 Download at Boykma.Com 323 Chapter 6: Forms Authentication Sharing Tickets between 1.1 and 2.0/3.5 It is likely that most organizations will need to run ASP.NET 1.1, 2.0, and 3.5 applications side by side for a few years. In many cases, if corporate developers integrate custom internal ASP.NET sites with webbased applications from third-party vendors, they may need to wait for the next upgrade from their vendors before moving a web application over to ASP.NET 2.0 or ASP.NET 3.5. It is worth mentioning that ASP.NET 3.5 uses the same runtime as that of ASP.NET 2.0. Upgrading an application from ASP.NET 2.0 to ASP.NET 3.5 requires no major changes at all, contrary to the case of upgrading an ASP.NET 1.1 to either ASP.NET 2.0 or ASP.NET 3.5. An application configured with .NET Framework 3.5 inside Visual Studio 2008 functions the same as an application running with .NET Framework 2.0 in Visual Studio 2008. However, the difference is in the added features that are part of ASP.NET 3.5 only (AJAX, LINQ, and so on). The bottom line here is that ASP.NET 3.5 and ASP.NET 2.0 applications share the same application pool on IIS 7.0 since the runtime is the same and, hence, whatever applies on ASP.NET 2.0 applies also on ASP.NET 3.5. You can accomplish both of the following scenarios when running in mixed environments: ❑❑ You can issue forms authentication tickets from ASP.NET 2.0 and ASP.NET 3.5 applications and the tickets will work properly when they are sent to an ASP.NET 1.1 application. ❑❑ You can issue forms authentication tickets from ASP.NET 1.1 applications and the tickets will work properly when they are sent to ASP.NET 2.0 and ASP.NET 3.5 applications. To interoperate tickets between the two versions, you must ensure the following: 1. ASP.NET 2.0 and ASP.NET 3.5 must be configured to use 3DES for encryption. Remember that by default ASP.NET 2.0 and ASP.NET 3.5 use AES for their encryption algorithm. 2. Both ASP.NET 1.1 and ASP.NET 2.0 or ASP.NET 3.5 must share common decryption and vali- dation keys. The first point was discussed earlier in the section on ticket security. However, the second point may not be immediately obvious for some types of applications. By default, both the validationKey and decryptionKey attributes are set to AutoGenerate,IsolateApps. This holds true for ASP.NET 1.1, ASP.NET 2.0 and ASP.NET 3.5. If a developer changes the settings to instead be AutoGenerate, that temporarily solves the problem of sharing the auto-generated key material across multiple ASP.NET applications on the same machine. However, when ASP.NET 2.0 is installed on a machine running ASP.NET 1.1 (that is, aspnet_regiis -I is run), the auto-generated key material is regenerated for ASP.NET 2.0. This means on a single web server that has both ASP.NET 1.1 and ASP.NET 2.0 running, setting any of the key attributes in to AutoGenerate is not sufficient. If you need to share forms authentication tickets between ASP.NET 1.1 and ASP.NET 2.0 or ASP.NET 3.5, you must use explicitly generated keys, and you must set the key values in the encryptionKey and decryptionKey attributes of . The section earlier on generating keys programmatically has sample code that makes it easy to generate the necessary values. 324 Download at Boykma.Com Chapter 6: Forms Authentication To demonstrate these concepts, use two simple applications. Both applications are initially configured as follows: Each application has a login page that simply issues a session-based forms authentication cookie after clicking a button on the page (interoperating 1.1 and 2.0 or 3.5 only works with cookies because there was no URL-based forms authentication in the base ASP.NET 1.1 product). With this basic web.config, forms authentication tickets will not work between the two applications because the defaults in are being used. If you try logging in against the 1.1 application and then change the address in the URL to reference a secure page in the 2.0 or 3.5 application, the ASP.NET 2.0 or ASP.NET 3.5 application returns you to the login page for the ASP.NET 2.0 or ASP.NET 3.5 page. The reason for this is twofold: The keys are different between the two applications, and ASP.NET 2.0 or ASP.NET 3.5 is using AES by default. To rectify this, place a section into both applications with explicit decryption and validation keys. In the case of ASP.NET 2.0 or ASP.Net 3.5, the section must also specify the correct encryption algorithm: decryptionKey is 48 characters long, which is the recommended length when using 3DES (48 characters = 24 bytes = three 8 byte keys of which only 56-bits are used for each of the three keys used in 3DES), validationKey is 40-characters long, which is the minimum length supported by this attribute. With the updated sections, you can now log in to the ASP.NET 1.1 application, and then change the URL to reference a 2.0 or 3.5 page without being forced to log in again. The reverse scenario also works properly: you can log in to the 2.0 or 3.5 application and then reference a 1.1 page without being forced to log in again. The only slight difference between tickets issued by ASP.NET 1.1 and ASP.NET 2.0 or ASP.NET 3.5 is the version property. If the forms authentication ticket is generated by ASP.NET 1.1, the Forms​Authentication​ Ticket.Version is set to 1. If the forms authentication ticket is generated by ASP.NET 2.0 or ASP.NET 3.5, then the property returns 2. Because neither ASP.NET 1.1 nor 2.0 or 3.5 do anything internally with the Version property (aside from packing and unpacking the value), the different values are innocuous. If for some reason you have business logic that depends on the value of the Version property, be aware that in a mixed ASP.NET environment there is no guarantee of a stable value. Download at Boykma.Com 325 Chapter 6: Forms Authentication Using Forms Authentication Across Different Content Types It has been mentioned several times throughout the book that one of the major advantages of running applications in the new IIS 7.0 integrated mode is the ability of ASP.NET runtime to process requests for non-ASP.NET resources. Added to this, the managed FormsAuthenticationModule is now integrated into IIS 7.0, which means that in addition to having the native authentication modules listed and registered inside IIS, you now have the managed FormsAuthenticationModule listed so that administrators or developers can enable/disable it the same way they enable/disable any other native authentication module. Figure 6-5 shows the list of authentication modules listed inside the IIS 7.0 Manager tool. Figure 6-5 There are some limitations on using the managed module. If the managed FormsAuthentication​ Module is enabled, you cannot enable any other native authentication module. There is a workaround to this limitation to enable both the native WindowsAuthenticationModule and the managed Forms​ AuthenticationModule, which you can read more about at http://mvolo.com/blogs/serverside/​ archive/2008/02/11/IIS-7.0-Two_2D00_Level-Authentication-with-Forms-Authentication-​ and-Windows-Authentication.aspx. But as a rule. and without any workarounds, you cannot enable the manage FormsAuthenticationModule while enabling any other native authentication module. The second limitation comes from the fact that IIS 7.0 cannot process any request if no native authentication module is enabled. The end result will be Access Denied! That is why when you want to enable ASP.NET FormsAuthenticationModule to authenticate requests to IIS 7.0, you should also enable the native AnonymousAuthenticationModule. This is an exception to the previously listed limitation, but this is a requirement by IIS 7.0. 326 Download at Boykma.Com Chapter 6: Forms Authentication IIS 7.0 can now make use of the powers of ASP.NET features when authenticating requests other than ASP.NET resources. For instance, if you have an ASP.NET application that contains .html, .asp, etc. file content types, you can easily protect them as if they were normal ASP.NET resources. The story starts with the new IIS 7.0 integrated mode where ASP.NET runtime can have access to all requests processed by IIS. Now enabling the managed FormsAuthenticationModule gives a chance for ASP.NET to handle the authentication and protection for all the content file types placed inside an application. To enable an application to use the managed FormsAuthenticationModule for non-ASP.NET resources, perform the following steps: 1. Configure the application to run under the DefaultAppPool. In other words, make sure the application is running in the integrated mode. 2. Configure the FormsAuthenticationModule to execute for all content types, not only ASP.NET resources. This is a trick that has been mentioned before. All you need to do is add the following into the application’s web.config file: If you do not want to add the above configuration elements manually, removing the Forms​ AuthenticationModule entry and re-adding it with the precondition attribute removed, you can make use of the rich IIS 7.0 Manager tool to configure a managed module to function properly on both managed and non-managed resources. Locate the Modules applet icon on the home page of the Web application you are configuring, and then double-click the Modules icon. A list of all the native and managed modules enabled for the application appears. Right-click the FormsAuthentication entry and choose Edit. Notice the checkbox displayed at the bottom of the dialog box that says “Invoke only for requests to ASP.NET applications or managed handlers.” This checkbox is selected by default, which means the FormsAuthenticationModule is enabled only for managed and ASP.NET resources, nothing else. To enable this module to function properly on non-managed and nonASP.NET resources, simply unselect the checkbox and notice the changes that were reflected back on the application’s web.config configuration file: Instead of removing the precondition attribute, the preceding configuration keeps the attribute as it is but replaces its value with an empty string, signaling that the managed module is now enabled for all content types, not only for managed resources. 3. In addition to enabling FormsAuthenticationModule to protect non-ASP.NET content, you also need to configure the UrlAuthorizationModule to execute and authorize non-ASP.NET content. What you need to do is add the following into the application’s web.config file. Again you can make use of the same alternative trick that was mentioned above to configure the managed UrlAuthorizationModule to function properly with managed and non-managed resources. When configuring the managed UrlAuthorizationModule to handle both native and managed resources, you might consider utilizing the native UrlAuthorizationModule introduced as part of the IIS 7.0 core engine. One of the major advantages of the native authorization module is its capability to handle all content types, whether managed or non-managed, and above all, its ability to understand the FormsAuthenticationTicket (hence its tight and seamless integration with the managed authentication module, FormsAuthenticationModule). The existence of this new native UrlAuthorizationModule gives you the opportunity either to keep on using the existing managed UrlAuthorizationModule for managed resources and enabling it for non-managed ones with some configuration settings, or to directly make use of the new native module that gives you a seamless integration with the managed Forms​ AuthenticationModule and protects your managed and non-managed resources with zero effort from your side. For a more in-depth explanation on the new native UrlAuthorizationModule, visit Chapter 3 and read the full explanation about the new native module introduced by IIS 7.0. 4. Finally, configure the application for forms authentication, and configure any needed attributes on the authentication configuration section in the application’s web.config file. 328 Download at Boykma.Com Chapter 6: Forms Authentication To the test the above configuration, I have included the aspnetForAllContent web application, which contains a mix of ASP.NET resources and non-ASP.NET resources. The application has been configured with forms authentication, and a “deny all anonymous users” authorization rule has been added under the configuration section to protect all the content included. Now if you try to access a protected .asp, .htm, or any other non-ASP.NET content types, you will notice that you are always redirected into the ASP.NET login page to provide your credentials before accessing any of the resources included in an application, whether the resources are ASP.NET or belong to any other type. Leveraging the UserData Property I will start out by saying up front that you can only leverage the UserData property for applications that run in cookie mode. Although the constructor for creating a FormsAuthenticationTicket with user data is public, there is no publicly available API for setting an instance of a FormsAuthentication​ Ticket onto a URL. As a result, the only way that the UserData can be used is if authentication tickets are sent in cookies. The nice aspect of the UserData property is that after you get custom data into the forms authentication ticket, the information is always there and available on all subsequent page requests. The problem in ASP.NET 1.1, ASP.NET 2.0 and ASP.NET 3.5 is that there is no single method that you can call wherein you supply both custom data for the UserData property and the username of the authenticated user. This oversight in ASP.NET 2.0 and ASP.NET 3.5 is somewhat unfortunate because I run across internal and external customers over and over again that need to store a few extra pieces of identification or personalization information after a user logs in. Storing this information in the forms authentication ticket is logical, and it can eliminate the need to cobble together custom caching mechanisms just to solve basic performance problems such as displaying a friendly first name and last name of a customer on every single web page. So, how do you store extra information in a forms authentication ticket and then issue the ticket in a way that all of the other settings (mainly the issue date and expiration date) are set to the correct values? More importantly, how do you do this without the need to hard-code assumptions into your code around cookie timeouts? In the FormsAuthentication class in ASP.NET 2.0, which is the same in ASP.NET 3.5, there is one glaring omission: you cannot retrieve the timeout attribute that is set in the element in configuration. Although you can technically retrieve this information with the strongly typed configuration classes in ASP.NET 2.0 and ASP.NET 3.5 (there is a FormsAuthenticationConfiguration class that provides strongly typed access to the values set in configuration), as was discussed in Chapter 5, you cannot use the strongly typed configuration classes when running in partial trust. The following solution uses a simple workaround to ensure that all of the forms authentication settings are still used when manually issuing a forms authentication ticket, and it does it in a way that will still work in partial trust applications. C# protected void Button1_Click(object sender, EventArgs e) { HttpCookie cookie = FormsAuthentication.GetAuthCookie(txtUsername.Text, false); Download at Boykma.Com 329 Chapter 6: Forms Authentication FormsAuthenticationTicket ft = FormsAuthentication.Decrypt(cookie.Value); //Cutom user data string userData = “John Doe”; FormsAuthenticationTicket newFt = new FormsAuthenticationTicket( ft.Version, //version ft.Name, //username ft.IssueDate, //Issue date ft.Expiration, //Expiration date ft.IsPersistent, userData, ft.CookiePath); //re-encrypt the new forms auth ticket that includes the user data string encryptedValue = FormsAuthentication.Encrypt(newFt); //reset the encrypted value of the cookie cookie.Value = encryptedValue; //set the authentication cookie and redirect Response.Cookies.Add(cookie); Response.Redirect( FormsAuthentication.GetRedirectUrl(txtUsername.Text, false),false); } VB.NET Protected Sub Button1_Click( _ ByVal sender As Object, _ ByVal e As EventArgs) Handles Button1.Click Dim cookie As HttpCookie = _ FormsAuthentication.GetAuthCookie(txtUsername.Text, False) Dim ft As FormsAuthenticationTicket = _ FormsAuthentication.Decrypt(cookie.Value) ‘Cutom user data Dim userData As String = “John Doe” Dim newFt As New FormsAuthenticationTicket(ft.Version, _ ft.Name, ft.IssueDate, ft.Expiration, ft.IsPersistent, _ userData, ft.CookiePath) ‘re-encrypt the new forms auth ticket that includes the user data Dim encryptedValue As String = FormsAuthentication.Encrypt(newFt) ‘reset the encrypted value of the cookie cookie.Value = encryptedValue 330 Download at Boykma.Com Chapter 6: Forms Authentication ‘set the authentication cookie and redirect Response.Cookies.Add(cookie) Response.Redirect( _ FormsAuthentication.GetRedirectUrl(txtUsername.Text, False), _ False) End Sub Because you need to ultimately issue a forms authentication cookie, the first step is to call Forms​ Authentication.GetAuthCookie, passing it the values that you would normally pass directly to FormsAuthentiction.RedirectFromLoginPage. This results in a cookie that has the correct settings for items such as cookie domain and cookie path. It also results in an encrypted cookie payload containing a forms authentication ticket. You can easily extract the FormsAuthenticationTicket by passing the cookie’s Value to the Decrypt method. At this point, you have a fully inflated FormsAuthenticationTicket with the correct values of Issue​ Date and ExpirationDate already computed for you. You can create a new FormsAuthentication​ Ticket instance based on the values of the FormsAuthenticationTicket that was just extracted from the cookie. The only difference is that for the userData parameter in the constructor, you supply the custom data that you want to be carried along in the ticket. In the case of the sample, I just stored a first name and last name as an example. Because the user data needs to fit within the limits of a single forms authentication ticket, there are some constraints on just how much information can be stuffed into this parameter. Internally, when you call FormsAuthentication.Encrypt, a 4K buffer is allocated to hold some of the interim results of encrypting the data. The net result is that that you cannot exceed roughly 2000 characters in the userData parameter if you need to call the Encrypt method. However, because the ultimate result needs to be stored in a cookie, you really only have 4096 bytes available for storing the entire ticket in the cookie. By the time the encryption bloat and hex string conversions occur, the realistic upper boundary on userData is around 900–950 characters. This still leaves a pretty hefty amount of space for placing information into the forms authentication ticket. And it is certainly enough space for common uses such as storing first name and last name, or storing a few IDs that are needed elsewhere in the application. In the sample code shown previously, the new FormsAuthentication instance is encrypted with a call to FormsAuthentication.Encrypt, and the result is placed in the Value property of the cookie that we started with. At this point, you now have a valid forms authentication cookie, with an encrypted representation of a FormsAuthenticationTicket that includes custom data. Notice that nowhere does the sample code need to rely on hard-coded values for determining date-time information. Also, the sample does not call in to any configuration APIs to look up any of the configuration values for the forms authentication feature. The last step in the sample is to add the forms authentication cookie into the response and then issue the necessary redirect. The Response.Redirect call shown in the sample roughly mirrors what occurs inside of that last portion of FormsAuthentication.RedirectFromLoginPage. Note that the Redirect overload that is used issues a “soft” redirect. The second parameter to the method is passed a false value, which means the remainder of the page will continue to run. Only when the page is done executing, and the remainder of the HTTP pipeline completes, will ASP.NET send back the redirect to the browser. Download at Boykma.Com 331 Chapter 6: Forms Authentication The call to GetRedirectUrl causes the forms authentication feature to find the appropriate value for the redirect URL based on information in the query-string (the familiar RedirectURL query-string variable you see in the address bar when you are redirected to a login page), or in the form post variables. Calling GetRedirectUrl eliminates the need for you to write any parsing code for determining the correct redirect target. You can run the sample application by attempting to access a simple home page that displays the UserData property on the ticket. C# //Display some user data FormsAuthenticationTicket ft = ((FormsIdentity)User.Identity).Ticket; Response.Write(“Hello “ + ft.UserData); VB.NET ‘Display some user data Dim ft As FormsAuthenticationTicket = (_ CType(User.Identity, FormsIdentity)).Ticket Response.Write(“Hello “ & ft.UserData) As you can see, after you jump through the hoops necessary to set the UserData in the ticket, it is very handy and easy to get access to it elsewhere in an application. Hopefully in future releases, ASP.NET will make it a bit easier to issue tickets with custom data as well as extending this functionality over to the cookieless case. Passing Tickets Across Applications Another title for this section could be “how to roll a poor man’s single sign-on (SSO) solution.” In ASP.NET 2.0 and ASP.NET 3.5, forms authentication includes the ability to pass forms authentication tickets across applications. Although prior to 2.0 you could create a custom solution that passed the forms authentication ticket around as a string, you had to write extra code to handle hopping the ticket across applications. ASP.NET 2.0 and ASP.NET 3.5 support setting the domain value of the forms authentication cookie from inside of configuration. ASP.NET 2.0 and ASP.NET 3.5 also add explicit support built into the APIs and the FormsAuthenticationModule for handling tickets that are passed using either query-strings or form posts. As long as you follow the basic conventions expected by forms authentication, the work of converting information sent in these alternative locations into a viable forms authentication ticket is automatically done by ASP.NET. Cookie Domain The ASP.NET 2.0 and ASP.NET 3.5 forms authentication configuration section includes a domain attribute. By default, this attribute is set to the empty string, which means that cookies issued by forms 332 Download at Boykma.Com Chapter 6: Forms Authentication authentication APIs will use the default value of the Domain property for a System.Web.HttpCookie. As a result, the Domain property of the cookie will be set to the full DNS address for the issuing website. For example, if a page is located at http://bhaidar-PC/login.aspx, the resulting cookie has a domain of bhaidar-PC. On the other hand, if the full DNS address for the server is used in the URL (http://bhaidar-PC.somedomain.com/login.aspx). Then the resulting cookie has its domain set to bhaidar-PC.somedomain.com. In ASP.NET 1.1, this was the only behavior supported by forms authentication, which made it problematic when attempting to share cookies across websites that only shared a portion of the domain name. For instance, you might need to authenticate users to bhaidar-PC.somedomain.com as well as someotherapp.somedomain.com, but the set of users is the same for both applications. With ASP.NET 2.0 and ASP.NET 3.5 this is easy to accomplish. Add the domain attribute to the element and set its value to the portion of the domain name that is shared across all of your applications. With this setting, each time a cookie is issued by forms authentication, the cookie’s domain value will be set to somedomain.com. As a result, the browser will automatically send the cookie anytime you request a URL where the network address ends with somedomain.com. Another nice side effect of this support for the domain attribute in ASP.NET 2.0 and ASP.Net 3.5 is that renewed forms authentication cookies (remember that with sliding expirations enabled, cookies can be renewed as they age) will also pick up the same value for the domain. In ASP.NET 1.1, if you enabled sliding expirations but you manually issued the forms authentication cookie with a different domain than the default, it was possible that the cookie would be automatically renewed by the FormsAuthenticationModule. When that happened in ASP.NET 1.1, it reissued the cookie and never set the domain attribute on the new cookie. Cross-Application Sharing of Ticket The ability to customize the domain of the forms authentication cookie is useful when all of your applications live under a common DNS namespace. What happens, though, if your applications are located in completely different domains? Companies that support multiple web properties, potentially with different branding, have to deal with this. The URLs of public websites are frequently chosen so as to be easy for customers to remember and, thus, are not necessarily chosen for purposes of DNS naming consistency. ASP.NET 2.0 and ASP.NET 3.5 have the ability to share forms authentication tickets across arbitrary sites by passing the forms authentication ticket around in the query-string or in a form post variable. This capability allows developers to intelligently flow authentication credentials across disparate ASP.NET sites without forcing a website user to repeatedly log in. Prior to ASP.NET 2.0, your only options were to manually create some type of workaround for this or to purchase a third-party vendor’s single sign on (SSO) product. A number of developers, though, really don’t need all the complexities and costs of full-blown SSO products. If the problem that you need to solve is primarily centered on sharing forms authentication tickets across multiple ASP.NET websites with different DNS namespaces, the support for passing forms authentication tickets across ASP.NET 2.0 and ASP.NET 3.5 applications will be a good fit. Download at Boykma.Com 333 Chapter 6: Forms Authentication That leads to the question of when wouldn’t you use the cross-application capabilities in ASP.NET 2.0 and ASP.NET 3.5? There are still valid reasons for using true SSO products, some of which are listed below: 1. You need to share authenticated users across heterogeneous platforms. For example you need to support logging users in across UNIX-based websites and ASP.NET sites. Clearly forms authentication won’t help here because there is no native support for the forms authentication stack on web platforms other than ASP.NET. 2 You need to share authenticated users across different untrusted organizations. This is a sce- nario where loose “federations” of different organizations need some way for website customers to seamlessly interact with different websites, but need to do so in a way that does not force the customer to constantly log in. For example, maybe a company wants the ability for a website customer to seamlessly navigate over to a parcel-tracking site to retrieve shipment information, and then over to a payment site to see the status of purchases and payments. Because each site is run by a different company, it is very hard to solve this problem today. There are a number of companies, including Microsoft, working on SSO solutions that can interoperate in a way allowing for a seamless authentication experience for this type of problem. 3. You may need to map the credentials of a logged-in user to credentials for other back-end data stores. For example, after logging in to a website the user may also have credentials in a mainframe system or a back-end resource planning system. Some SSO products support the ability to map authentication credentials so that a website user logs in once and then is seamlessly reauthenticated against these types of systems. As you can see from this partial list, most of the SSO scenarios involve more complexity in the form of other companies or other systems that are external to the website. Many extranet and internet sites don’t need to solve these problems, or can live with comparatively simple solutions for reaching into back-end data stores. For these types of sites, the cross-application support in forms authentication is a lower-cost and easier solution to the single sign on problem. How Cross-Application Redirects Work By default, the “SSO-lite” functionality in ASP.NET 2.0 and ASP.NET 3.5 is not enabled. To turn it on, you need to set the enableCrossAppRedirects attribute to true: Doing so turns on a few pieces of logic within forms authentication. First, the FormsAuthentication​ .RedirectFromLoginPage method has extra logic to automatically place a forms authentication ticket into a query-string variable when it detects that it will be redirecting outside of the current application. Second, the FormsAuthenticationModule will look on the query-string and in the form post variables for a forms authentication ticket if it could not find a valid ticket in the other standard locations (that is, in a cookie or embedded in the URL, for the cookieless case). Because cookie-based tickets automatically flow across applications that share at least a portion of a DNS namespace, you really only need to set enableCrossAppRedirects to true for the following cases: ❑❑ You need to send a forms authentication ticket between applications that do not share any portion of a DNS namespace. In this case, the “domain” attribute isn’t sufficient to solve the problem. 334 Download at Boykma.Com Chapter 6: Forms Authentication ❑❑ You need to send a cookieless ticket between different applications, regardless of whether or not the applications share the same DNS namespace. Cookieless tickets, by their very nature, are limited to only URLs in the current application. Cookieless Cross-Application Behavior Examine the cookieless case first. You can create two sample applications and in configuration set up forms authentication and the authorization rules as follows: With this configuration, both applications are forced to use cookieless tickets. Additionally, both applications share common key information which ensures that a ticket from one application is consumable by the other application. To focus on the cross-application redirect issue, we will keep the rest of the application very simple. Both applications will have a default.aspx page, and a login page. Both login pages (for now) will simply issue a forms authentication ticket for a fixed username and then pass the user back to the original requesting URL: C# FormsAuthentication.RedirectFromLoginPage(“testuser”, false); VB.NET FormsAuthentication.RedirectFromLoginPage(“testuser”, False) After you end up on default.aspx, there is a button you can click to redirect yourself over to the other application: C# Response.Redirect(“/cookielessAppB/default.aspx”); VB.NET Response.Redirect(“/cookielessAppB/default.aspx”) Download at Boykma.Com 335 Chapter 6: Forms Authentication The preceding code is in the sample application called cookielessAppA, so default.aspx redirects over to the other sample application: cookielessAppB. If you were to run both sample applications, and try to seamlessly ping-pong between the two applications, you would find yourself constantly logging in. The culprit of course is that Response.Redirect that punts you to the other application; when that redirect is issued, the cookieless credentials embedded in the current URL are lost. Unfortunately, you can’t just call one API or use some new parameter on the Redirect method to solve this problem when running in cookieless mode. Although FormsAuthentication.Redirect​ From​LoginPage has logic to store a ticket on the query-string, the scenario above is one where you click on a link inside of one application, and it takes you over to a second application. For this case, you need a wrapper around Response.Redirect that includes the logic to pass the forms authentication ticket along with the redirection. I created a simple query-string wrapper: C# public static class RedirectWrapper { public static string FormatRedirectUrl(string redirectUrl) { HttpContext c = HttpContext.Current; if (c == null) throw new InvalidOperationException(“You must have an active context to perform a redirect”); //Don’t append the forms auth ticket for unauthenticated users or //for users authenticated with a different mechanism if (!c.User.Identity.IsAuthenticated || !(c.User.Identity.AuthenticationType == “Forms”)) return redirectUrl; //Determine if we need to append to an existing query-string or not string qsSpacer; if (redirectUrl.IndexOf(“?”) > 0) qsSpacer = “&”; else qsSpacer = “?”; //Build the new redirect URL string newRedirectUrl; FormsIdentity fi = (FormsIdentity)c.User.Identity; newRedirectUrl = redirectUrl + qsSpacer + FormsAuthentication.FormsCookieName + “=” + FormsAuthentication.Encrypt(fi.Ticket); return newRedirectUrl; } } 336 Download at Boykma.Com Chapter 6: Forms Authentication VB.NET Public NotInheritable Class RedirectWrapper Private Sub New() End Sub Public Shared Function FormatRedirectUrl(ByVal redirectUrl As String) As String Dim c As HttpContext = HttpContext.Current If c Is Nothing Then Throw New InvalidOperationException(“You must have an active context to perform a redirect”) End If ‘Don’t append the forms auth ticket for unauthenticated users or ‘for users authenticated with a different mechanism If (Not c.User.Identity.IsAuthenticated) OrElse _ Not(c.User.Identity.AuthenticationType = “Forms”) Then Return redirectUrl End If ‘Determine if we need to append to an existing query string or not Dim qsSpacer As String If redirectUrl.IndexOf(“?”) > 0 Then qsSpacer = “&” Else qsSpacer = “?” End If ‘Build the new redirect URL Dim newRedirectUrl As String Dim fi As FormsIdentity = CType(c.User.Identity, FormsIdentity) newRedirectUrl = redirectUrl & _ qsSpacer & _ FormsAuthentication.FormsCookieName & _ “=” & _ FormsAuthentication.Encrypt(fi.Ticket) Return newRedirectUrl End Function End Class Given a query-string, the static method FormatRedirectUrl makes a few validation checks and then appends a query-string variable with the forms authentication ticket to the URL. If the current request doesn’t have an authenticated user, or if it’s not using forms authentication, calling the method is a no-op. Assuming that there is a forms-authenticated user, the method determines whether or not it needs to add a query-string to the current URL, or if instead it just needs to append a query-string variable (there may already be one or more query-strings on the URL, hence the need to check for this condition). Last, the method reencrypts the current user’s forms authentication ticket back into a string, and it places it on the query-string. Notice how the value of FormsAuthentication.FormsCookieName is used as the name of the query-string variable. Even though the code isn’t really sending a cookie, the FormsCookieName is the identifier used for a forms authentication ticket regardless of whether the ticket is in the query-string, in a form post variable or contained in a cookie. Download at Boykma.Com 337 Chapter 6: Forms Authentication To use the new helper method, we can rework the previous redirect logic to look like this: C# Response.Redirect( RedirectWrapper.FormatRedirectUrl(“/cookielessAppB/default.aspx”)); VB.NET Response.Redirect( RedirectWrapper.FormatRedirectUrl(“/cookielessAppB/default.aspx”)) You can update both sample applications to include the new helper class in their App_Code directories. Also, update the forms authentication configuration to enable cross-application redirects. This is necessary for the forms authentication module to recognize the incoming ticket on the query-string properly. Now when you use both applications, you can seamlessly ping-pong between both applications without being challenged to log in again. Each hop from application A to application B results in a redirect underneath the hood that includes the ticket on the query-string: http://localhost/cookielessAppB/default.aspx?.ASPXAUTH=F2CB90DA66DE1044FEEE4FE676 AB6C1226EF04F5FDE104002CEA29448E2CC0CD3AF7BA33E4022C5E786BAD23F98163F708AB21A52893 9502ADBCAB5031C918F47AD1A317AC183883 The FormsAuthenticationModule detects this and properly converts the query-string variable back into a cookieless ticket embedded on a URL. Due to the reliance on redirect behavior, you can’t post any data from one application to the other. Instead, you have to pass information between applications with query-string variables. Even if you attempt to use a form post as a mechanism for transferring from one application to another, you can’t avoid at least one redirect. When the FormsAuthenticationModule in the second application issues a forms authentication ticket based on the ticket that was carried in the query-string, the module issues a redirect to embed the new ticket onto the URL. The only way to avoid a redirect in this case is if you run in cookie mode, which we shall see shortly. As an aside, there is one slight quirk in how this all works. Remember earlier in the discussion on cookieless tickets where it was mentioned that the requireSSL attribute in the element is ignored when using cookieless tickets? If you enable cross application redirects, the requireSSL attribute still affects the FormsAuthenticationModule. Under the following conditions, the Forms​ AuthenticationModule will ignore any query-string or forms variable containing a ticket: ❑❑ The requireSSL attribute is set to true. ❑❑ The module could not find a ticket either in a cookie or embedded in a URL, and hence reverted to looking in the query-string and forms variable collection. ❑❑ The current connection is not secured with SSL. If you think you have cross-application redirects setup properly, and you are still being challenged with a login prompt, double-check and make sure that you have not set requireSSL to true and then attempted to send the ticket to another application over a non-SSL connection. 338 Download at Boykma.Com Chapter 6: Forms Authentication Cookied Cross-Application Behavior You can use a similar application to the cookieless sample to also show cross-application redirects in the cookied case. Again using two sample applications, both applications need to share a common configuration: To simulate isolation of the forms authentication cookies, each application explicitly sets the path attribute as shown above. Because this sample uses cookies, the path attribute prevents the browser from sending the forms authentication cookie for one application over to the second application. Remember that setting the path attribute only takes effect when using cookied modes (for example, setting the path attribute would have no effect on the previous cookieless example). For starters, we will use the same redirection helper as we did earlier, and pages in both applications will issue a Response.Redirect to get to the second application. When you run the sample applications, you get almost the same result as the cookieless applications. You can bounce around between applications without the need to log in again. However, one noticeable difference is the lack of a second redirect each time you transition from one application to another. When the FormsAuthenticationModule converts the query-string variable into a forms authentication ticket encapsulated inside of a cookie, it does not need to issue a redirect. Instead, it just sets a new cookie in the response, and the remainder of the request is allowed to execute. As a result, when you transition from application A to application B, the URL in the browser address bar still retains the query-string variable used during the redirect: http://localhost/cookiedAppB/default.aspx?.ASPXAUTH=23CB12E603239A53830866D67D38DE6 E8AAAA3647A05220FB278A5B6A3A0C0927FC498D3E6ED46AEBD7EF770AC3359CABE08EDC63385D8C058 B58D0C63782A27F948A8A8BFF5DFE9CE2C78463C68E1C0EB390B6C89CB594D21564EF94B2866CA112AF E132F904FF87FF728B6DD3A48E6 Although it looks a bit strange, this is actually innocuous. After you start navigating around in the second application, the query-string variable will go away: 1. When the current page posts back to itself, the query-string variable will flow down to the application. 2. The FormsAuthenticationModule first looks for valid tickets in cookies and embedded in the URL. Because it finds a valid ticket in a cookie, it never makes it far enough to look at the querystring variable. 3. The current page runs. 4. Eventually you click on a link or trigger a redirect to some other page in the application. When this occurs the query-string is not sent along with the request, and as a result other pages in the application won’t have the ticket sitting in the address bar. Download at Boykma.Com 339 Chapter 6: Forms Authentication Because the point at which step 4 occurs is probably not deterministic (a website user may be able to enter into the application from any number of different pages), the query-string variable can end up in the address bar for any of your entry pages. As with cookieless cross application redirection, if you happen to set requireSSL to true in your applications, the hop from one application to another will cause the FormsAuthenticationModule to check the secured state of the connection. If the module detects that the cross-application redirect occurred on a non-SSL connection, it will throw an HttpException, just as it would for the cookieless scenario. Unlike the cookieless case, though, you do have another option for hopping credentials from one application over to another. You can choose to post the forms authentication ticket from one application to another because you don’t need to worry about the extra redirect the FormsAuthentication​Module performs when embedding the ticket into the URL. To show this, create another page in the first application: Untitled Page


This page markup takes advantage of a feature that was originally introduced in ASP.NET 2.0 called cross-page postings. Although this sample application is not showing the primary purpose of cross-page posting (which is posting between two different pages within the same application), it turns out that you can use cross-page posting just as well to make it easier to post form data across applications. The markup above has set the PostBackUrl property on a standard Button control to a URL located in the second sample application. By doing so, ASP.NET injects some extra information into the page that causes the page to post back to the second application. In addition to using cross-page posting, the code-behind for the page sets some values for the hidden control that is on the page: C# protected void Page_Load(object sender, EventArgs e) { this.Hidden1.ID = FormsAuthentication.FormsCookieName; this.Hidden1.Value = 340 Download at Boykma.Com Chapter 6: Forms Authentication FormsAuthentication.Encrypt(((FormsIdentity)User.Identity).Ticket); } VB.NET Protected Sub Page_Load( _ ByVal sender As Object, _ ByVal e As EventArgs) Handles Me.Load() Me.Hidden1.ID = FormsAuthentication.FormsCookieName Me.Hidden1.Value = _ FormsAuthentication.Encrypt((CType(User.Identity, FormsIdentity)).Ticket) End Sub The hidden control has its ID set to the same value as the forms authentication cookie. This is necessary because when the request flows to the second application, one of the places the FormsAuthentication​ ​Module will look for a forms authentication ticket is in Request.Form[“name of the forms authentication cookie”]. The value of the hidden control is set to the encrypted value of the FormsAuthenticationTicket for the current user. This is the same operation we saw earlier for the redirection scenarios, with the difference being that in this sample the forms authentication ticket is being packaged and stored inside of a hidden form variable rather than a query-string variable. When you request this page from the first application in the browser, viewing the source shows how everything has been lined up for a successful cross-page post. An abbreviated version of the
element is shown here:
The forms authentication ticket is packaged up in the hidden form variable. You can also see that the form’s action is set to PostToAnotherApplication.aspx, which at first glance doesn’t look like a page in another application. The form will actually post to another application because the button on the form has a click handler that calls WebForm_DoPostBackWithOptions. This method is one of the many ASP.NET client-side JavaScript methods returned from webresource.axd (webresource.axd is the replacement for the JavaScript files that you used to deploy underneath the aspnet_client subdirectory back in ASP.NET 1.1 and 1.0). Download at Boykma.Com 341 Chapter 6: Forms Authentication When you press the button on this page, two things occurs: 1. The WebForm_DoPostBackWithOptions client-side method sets the action attribute on the client-side form to the value /cookiedAppB/ReceivePostFromAnotherApplication.aspx. 2. The client-side method returns, at which point because the button is of type “submit,” the client-side form is submitted by the browser, using the “action” that was just set. As a result of this, you have a form-submit from a page in Application A flowing over to application B. When the request hits application B, it starts running through the HTTP pipeline. The Forms​ AuthenticationModule sees the request, and attempts to find a forms authentication ticket. Eventually, the module looks in Request.Form[“.ASPXAUTH”] for a forms authentication ticket. Because there is a hidden field on the form called .ASPXAUTH, the module is able to find the string value stored there. The module then converts the string value into a forms authentication ticket and sets a cookie on the response that contains this ticket. At this point the request continues to run, which in the case of the sample application results in a call on the page to: C# Response.Write(“The posted value was: “ + Request.Form[“txtSomeInfo”]); VB.NET Response.Write(“The posted value was: “ & Request.Form(“txtSomeInfo”)) If you run the sample application, you will see that the preceding line of code will successfully play back to you whatever value you typed into the text box back in application A. The other nice thing about this approach is that not only are posted variables retained across the two applications, when you end up on the page in the second application there isn’t the somewhat odd (maybe unsettling?) behavior of the authentication ticket showing up in the address bar of the browser. Additionally, if you view the source of the second page in the browser, there isn’t any authentication ticket there either. For both of these reasons, when running sites with cookie-based forms authentication, POST-based transfers of control between applications are preferred to the approach that relies on calling Response.Redirect. One last comment on the cross-page posting case: remember that you always need to explicitly set the keys in the element for all participating applications. Without this, the forms authentication ticket in the hidden field will not be decryptable in the second application. Cookie-based “SSO-Lite” Now that you have seen the various permutations of passing forms authentication tickets between applications, let’s tie the concepts together with some sample applications that use a central login form. This approach is conceptually similar to how Passport works with all tickets being issued from central login application. Note that this design only works with cookie-based forms authentication because it relies on issuing forms authentication cookies that can authenticate the browser back to the original application. Websites that use cookieless forms authentication need more explicit code inside of each application due to the need to manually create some approach for hopping authentication tickets from one application to another. 342 Download at Boykma.Com Chapter 6: Forms Authentication The general design of our “hand-rolled” single sign-on solution is shown in Figure 6-6. Step 1: Attempt to access secured pages in Application A Browser User Step 8: User access another application. Application A Step 4: Central login app sends back login form. Step 5: Browser user posts back credentials Step 2: App A redirects to local login page Local Login.aspx Application B Step 3: Local login page redirects to central login app Step 6: Central login page redirects to self. Step 9: App B redirects to local login page Local Login.aspx Central login management Central Login.aspx Step 10: Local login page redirects to central login app. Step 7: Central login page redirects back to app A with credentials on the query string Figure 6-6 Download at Boykma.Com Step 11: Central login app detects user already logged in. Issues ticket on query string and redirects back to app B. 343 Chapter 6: Forms Authentication The desired behavior of the solution is described in the following list: 1. A user attempts to access a secured application, in this case Application A. At this point, the user has not logged in anywhere and thus has no forms authentication tickets available. 2. When the request reaches application A, it detects that the application allows authenticated users only. As a result, it redirects the browser to a login page that is local to the application. 3. The local login page does not actually send back a login form to the user at this point. Instead, the local login form places some information onto the query-string and then redirects to a central login application. 4. The central login application detects that the user has never logged in against it, and so it redi- rects the user to a login page in the central login application. This is the only point at which the browser user ever sees a login UI. 5. At this point the browser user enters credentials into a form and submits the form back to the central login application. 6. Assuming that the credentials are valid, the login page in the central login application redi- rects back to itself. This is because the login page handles both interactive logins and noninteractive logins. 7. When the login page redirects to itself, it detects that the user already has a valid forms authen- tication ticket for the central login application. So instead, the login page clones the forms authentication ticket and sends this new ticket by way of a redirect back to application A. In Application A, the FormsAuthenticationModule will see the ticket on the query-string, convert it into a cookie, and then start running the original page that the user was attempting to access back in step 1. 8. Some time later, the user attempts to access a secured page in application B. 9. Because there is no forms authentication ticket for application B, it redirects to the local login page. As with application A though, the local login page just exists to place information on the query-string and redirect to the central login application. 10. When the redirect reaches the login page in the central login application, the forms authentica- tion ticket issued back in step 6 will flow along with the request. As a result, the login page detects that the user already logged in. 11. Rather than sending back a login form, the login page creates another clone of the forms authen- tication ticket and places it on a query-string. It then redirects back to application B. 12. The FormsAuthenticationModule in application B converts the forms authentication ticket on the query-string into a forms authentication cookie. The original page that the user requested back in step 8 then runs. You can see that the primary underpinning of the SSO-lite solution in forms authentication is the ability to pass forms authentication tickets across disparate applications. A website user logs in against a central application, which results in a forms authentication cookie being sent to the user’s browser. That forms authentication ticket becomes the master authentication ticket for all subsequent attempts to access other sites. Whenever a participating website redirects back into the central login application, the master forms authentication cookie is sent by the user’s browser to the login page in the central application. The central login page can then crack open this ticket and extract most of the values in it, and create a new 344 Download at Boykma.Com Chapter 6: Forms Authentication forms authentication ticket. The new ticket is what is packaged on the query-string and sent back to the original application by way of a redirect. The benefit of generating application-specific forms authentication tickets off of the central application’s forms authentication ticket is that all participating applications receive a forms authentication ticket with a common set of issue and expiration dates. It is the central login application that defines for how long the master ticket is valid (if sliding expirations are even allowed). The cloned tickets for all of the participating applications simply reflect these settings as established in the central login application. Now that you have reviewed the conceptual design, it’s time to drill into the actual implementation. There are two important pieces of information that all participating applications need to send over to the central application: ❑❑ The URL of the page that was originally requested in the application ❑❑ The desired cookie path that should be used when creating a forms authentication ticket in the participating application The first piece of information is pretty intuitive. Because you want your SSO-lite solution to roughly mirror the standard forms authentication behavior, we need the website user to eventually end up on the page that was originally requested. However, the second piece of information is very important to get right because the solution will be issuing forms authentication tickets in one place (the central login application), but the ticket needs to be converted into a valid cookie in a completely different place (the FormsAuthenticationModule of the participating application). It turns out that the login in forms authentication for handling cross-application redirects is dependent on the CookiePath property of FormsAuthenticationTicket. When a FormsAuthenticationModule receives a ticket on the query-string, it does not look at the path attribute set in the element for the application. Instead, when the module cracks open the ticket that was sent on the query-string, it uses the CookiePath that it finds there as the value for the Path property on the resulting forms authentication HttpCookie. In our SSO-lite solution, the two necessary pieces of information are passed from participating applications to the central login application with two query-string variables: ❑❑ CustomCookiePath: Each participating application sets this value to FormsAuthentication​ .CookiePath. That has the effect of ensuring the forms authentication ticket issued inside of each application actually uses the path as set in each application’s configuration. ❑❑ CustomReturnUrl: Each participating application sets this value to the original URL that the website user was attempting to access. The central login application eventually issues a redirect back to this URL. For those of you that poke around a bit in the internal workings of forms authentication, you may be wondering why the solution needs a custom definition of a return URL. Whenever forms authentication performs its automatic redirect-to-login-page logic, there is a query-string variable called ReturnUrl. You cannot overload this query-string variable for the purposes of cross-application redirects because forms authentication only places a server-relative virtual path into this variable. Forms authentication does not have the ability in ASP.NET 2.0 and ASP.NET 3.5 to add the DNS or server name into the ReturnUrl variable (that is, forms authentication never prepends http://some.server.address.here/ to this variable). Download at Boykma.Com 345 Chapter 6: Forms Authentication An SSO-lite solution would not be very useful, though, if the only return URLs sent to the central login application were to other applications deployed on the same IIS server. In fact, if that were the only problem you were trying to solve, chances are all you would need to do is set the domain attribute in configuration. As a result, the SSO-lite solution uses the CustomReturnUrl variable to hold the fully qualified address of the original page the website user was attempting to access. This ensures that the central login application can exist in a completely different DNS namespace from any of the participating applications. Sample Participating Application The web.config for a participating application is defined as shown here: The bolded portions of the configuration require some explanation. First, the variable defines the full URL needed to reach the login page in the central login application. You would need to set this in the configuration of every participating application so that applications know where to send the authentication redirect. The enableCrossAppRedirects setting is necessary so that the FormsAuthenticationModule inside the application will look in the query-string or form post variables for a ticket. With this setting turned on, the participating application can successfully convert tickets send from the central application back into an application-specific forms authentication ticket. Last, note that slidingExpiration is set to false. Because the central login application issues the master forms authentication ticket, it is the timeout and slidingExpiration settings of the central login application that take precedence. You don’t want participating applications to be renewing forms authentication tickets; rather, you want the central login application to do this for you. 346 Download at Boykma.Com Chapter 6: Forms Authentication Because the configuration above denies access to all anonymous users, any attempt to access a page in the application results in a redirect to the local login page. The local version of Login.aspx is shown here: C# protected void Page_Load(object sender, EventArgs e) { Redirector.PerformCentralLogin(this); } VB.NET Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) _ Handles Me.Load Redirector.PerformCentralLogin(Me) End Sub It is intentionally kept simple because you don’t want to duplicate the redirection login in every single application. In this case, there is a static helper class called Redirector that has a single helper method called PerformCentralLogin. C# public static class Redirector { //snip…. private static string centralLoginUrl; static Redirector() { centralLoginUrl = ConfigurationSettings.AppSettings[“centralLoginUrl”]; //snip… } public static void PerformCentralLogin(Page p) { string redirectUrl = FormsAuthentication.GetRedirectUrl(string.Empty, false); //snip… string baseServer = p.Request.Url.DnsSafeHost; string customRedirectUrl = “http://” + baseServer + redirectUrl; p.Response.Redirect( centralLoginUrl + “?CustomReturnUrl=” + p.Server.UrlEncode(customRedirectUrl) + “&CustomCookiePath=” + p.Server.UrlEncode(FormsAuthentication.FormsCookiePath)); } } Download at Boykma.Com 347 Chapter 6: Forms Authentication VB.NET Public NotInheritable Class Redirector //snip… Private Shared centralLoginUrl As String Shared Sub New() centralLoginUrl = ConfigurationSettings.AppSettings(“centralLoginUrl”) //snip… End Sub Public Shared Sub PerformCentralLogin(ByVal p As Page) Dim redirectUrl As String = _ FormsAuthentication.GetRedirectUrl(String.Empty, False) //snip… Dim baseServer As String = p.Request.Url.DnsSafeHost Dim customRedirectUrl As String = “http://” & baseServer & redirectUrl p.Response.Redirect(centralLoginUrl & _ “?CustomReturnUrl=” & _ p.Server.UrlEncode(customRedirectUrl) & _ “&CustomCookiePath=” & _ p.Server.UrlEncode(FormsAuthentication.FormsCookiePath)) End Sub End Class For simplicity, I placed the static class definition into the App_Code directory of each participating application. In a production application, you would take this one step further and at least compile the code into a bin-deployable assembly, if not the GAC. When the Redirector class is first used, the static constructor runs. For now, the code snippet shows only part of the work in the static constructor where it fetches the central login URL once for future use. The single parameter to the PerformCentralLogin method is a reference to the current page. This ensures the helper method has access to any request-specific objects necessary to build up the redirect information. The PerformCentralLogin method fetches the redirect URL using FormsAuthentication​ .GetRedirectUrl. At this point, calling GetRedirectUrl works because it returns the virtual path to the originally requested page. However, as noted earlier, the path lacks the server information necessary to allow redirects to work against any arbitrary set of servers and DNS namespaces. Ignoring some other functionality for a second, the method fetches the server portion of the current URL. With both the server’s address, and the virtual path in hand, the method constructs the fully qualified redirect path. The method can now redirect to the central login application’s login page, including the fully qualified return URL in the CustomReturnUrl query-string variable and the correct cookie path information for the forms authentication ticket in the CustomCookiePath query-string variable. So, the net result of the original call in the Load event of Login.aspx is that the participating application silently constructs and issues a redirect into the central login application. No user interface for login is ever returned by a participating application. Let’s return the code that was snipped out earlier. The following includes bolded code that shows some additional logic: 348 Download at Boykma.Com Chapter 6: Forms Authentication C# public static class Redirector { private static Dictionary pages; private static string centralLoginUrl; static Redirector() { centralLoginUrl = ConfigurationSettings.AppSettings[“centralLoginUrl”]; //Register page mappings to force correct casing for the cookie //that will eventually be issued. pages = new Dictionary(StringComparer.InvariantCultureIgnoreCase); pages.Add(“/AppAUsingCentralLogin/Default.aspx”, “/AppAUsingCentralLogin/Default.aspx”); pages.Add(“/AppAUsingCentralLogin/AnotherPage.aspx”, “/AppAUsingCentralLogin/AnotherPage.aspx”); } public static void PerformCentralLogin(Page p) { string redirectUrl = FormsAuthentication.GetRedirectUrl(string.Empty, false); //Fix the casing of the redirect URL to prevent problems with new cookies //being issued for a request with incorrect casing on the URL. redirectUrl = pages[redirectUrl]; string baseServer = p.Request.Url.DnsSafeHost; string customRedirectUrl = “http://” + baseServer + redirectUrl; p.Response.Redirect( centralLoginUrl + “?CustomReturnUrl=” + p.Server.UrlEncode(customRedirectUrl) + “&CustomCookiePath=” + p.Server.UrlEncode(FormsAuthentication.FormsCookiePath)); } } VB.NET Public NotInheritable Class Redirector Private Shared pages As Dictionary(Of String, String) Private Shared centralLoginUrl As String Shared Sub New() centralLoginUrl = ConfigurationSettings.AppSettings(“centralLoginUrl”) ‘Register page mappings to force correct casing for the cookie ‘that will eventually be issued. Download at Boykma.Com 349 Chapter 6: Forms Authentication pages = _ New Dictionary(Of String, String)(StringComparer.InvariantCultureIgnoreCase) pages.Add(“/AppAUsingCentralLogin_vb/Default.aspx”, _ “/AppAUsingCentralLogin_vb/Default.aspx”) pages.Add(“/AppAUsingCentralLogin_vb/AnotherPage.aspx”, _ “/AppAUsingCentralLogin_vb/AnotherPage.aspx”) End Sub Public Shared Sub PerformCentralLogin(ByVal p As Page) Dim redirectUrl As String = FormsAuthentication.GetRedirectUrl(String.Empty, False) ‘Fixup the casing of the redirect URL to prevent problems ‘with new cookies being issued for a request with ‘incorrect casing on the URL. redirectUrl = pages(redirectUrl) Dim baseServer As String = p.Request.Url.DnsSafeHost Dim customRedirectUrl As String = “http://” & baseServer & redirectUrl p.Response.Redirect(centralLoginUrl & _ “?CustomReturnUrl=” & _ p.Server.UrlEncode(customRedirectUrl) & _ “&CustomCookiePath=” & _ p.Server.UrlEncode(FormsAuthentication.FormsCookiePath)) End Sub End Class The bolded code in the preceding code deals with a quirk in cookie handling. If you depend on setting the Path property of an HttpCookie, the path information is case-sensitive. For many developers, using forms authentication this isn’t an issue because forms authentication defaults to a path of /. However, when putting together this sample, there were some frustrating moments before realizing that some of the test URLs I was using had incorrect casing compared to the path of the forms authentication cookie. If you plan to create your own SSO-lite solution, and if you intend to segment forms authentication tickets between applications through the use of a cookie’s path property, you need to be very careful about how URLs are handled in your code. In the case of the sample SSO-lite solution, the bolded code is a simple workaround for ensuring proper casing. The helper class holds a dictionary containing every URL in the application. The trick here is that the dictionary uses a case-insensitive string comparer, and it uses the invariant culture. This means whenever a lookup is made into the dictionary, the key comparison ignores case, and treats culture-sensitive characters in a neutral manner. When the PerformCentralLogin method runs, it always takes the redirect URL as returned from forms authentication and converts it into the correct casing. The theory here is that if this method is called, it is very likely that it is being called due to an end user (like myself) accidentally typing in the wrong casing for a URL in the IE address bar. By performing a lookup into the static dictionary, the method can convert any arbitrary casing on the redirect URL into a URL with correct casing. Because the SSO-lite solution does partition forms authentication tickets with paths other than / (from the configuration a few pages 350 Download at Boykma.Com Chapter 6: Forms Authentication back, the current application we are looking at uses a cookie path of /AppAUsingCentralLogin), it is important to perform this conversion prior to sending the redirect URL to the central login application. Central Login Application The configuration for the central login application pretty much mirrors that of the participating applications. Unlike the participating applications, the central login application does not register any URL in the section. In fact, the SSO-lite solution shown here has zero knowledge of any of the other participating applications. The bolded attributes in the element are of interest because these settings not only define behavior for the master forms authentication ticket issued by the central login application, the settings also influence the ticket behavior for the participating application. Of course, enableCrossApp​ Redirects is set to true because without that, there is no way to hop tickets between applications. The path attribute ensures that the forms authentication ticket for the central login application stays in the central login application. This is why I refer to the forms authentication ticket from the central login application as the “master” forms authentication ticket. After it is issued, the cookie never flows to any other application. The slidingExpiration and timeout attributes define the expiration behavior for the master forms authentication ticket. Because the master ticket is also cloned and used as the source for tickets sent to other participating applications, this means these attributes also define the expiration behavior for all other applications. In the case above, the central login application is using the standard timeout of 30 minutes, and it is allowing sliding expirations. Remember, though, that slidingExpiration is always set to false in all of the participating applications. This point will be expanded on below when I cover the login page. Download at Boykma.Com 351 Chapter 6: Forms Authentication The login page in the central login application normally would have the user interface for collecting credentials and validating them. However, because this is just a sample that focuses on the mechanics of passing tickets around, the actual “login” on the page is pretty basic and uses a fixed credential: C# protected void Button1_Click(object sender, EventArgs e) { FormsAuthentication.SetAuthCookie(“testuser”, false); string redirectUrl = Request.QueryString[“CustomReturnUrl”]; string cookiePath = Request.QueryString[“CustomCookiePath”]; Response.Redirect(“Login.aspx?CustomReturnUrl=” + redirectUrl + “&CustomCookiePath=” + cookiePath, true); } VB.NET Protected Sub Button1_Click( _ ByVal sender As Object, _ ByVal e As EventArgs) Handles Button1.Click FormsAuthentication.SetAuthCookie(“testuser”, False) Dim redirectUrl As String = Request.QueryString(“CustomReturnUrl”) Dim cookiePath As String = Request.QueryString(“CustomCookiePath”) Response.Redirect(“Login.aspx?CustomReturnUrl=” & redirectUrl & “&CustomCookiePath=” & cookiePath, True) End Sub Rather than calling FormsAuthentication.RedirectFromLoginPage, the button click handler for login calls SetAuthCookie. Calling SetAuthCookie ensures that the master forms authentication cookie is set in the Response, but it also allows the login page to do other work and then programmatically issue a redirect. Because the CustomReturnUrl and CustomCookiePath attributes are still needed, the click event handler simply moves the values from the inbound Request query-string to the query-string variables on the redirect. The important thing to note about the click event handler is that it will only be called when an interactive login is required. The very first time website users enter any participating site, they will end up with the interactive login and their response will flow to the click event handler. However, as the following code shows, the login page also supports noninteractive login: C# protected void Page_Load(object sender, EventArgs e) { //If the user is already authenticated, then punt them back //to the original application, but place a new forms authentication //ticket on the query string. if (User.Identity.IsAuthenticated == true) { //This information comes from the forms authentication cookie for the 352 Download at Boykma.Com Chapter 6: Forms Authentication //central login site. FormsIdentity fi = (FormsIdentity)User.Identity; FormsAuthenticationTicket originalTicket = fi.Ticket; //For sliding expirations, ensure the ticket is periodically refreshed. DateTime expirationDate; if (FormsAuthentication.SlidingExpiration == true) { TimeSpan timeout = originalTicket.Expiration.Subtract(originalTicket.IssueDate); expirationDate = originalTicket.IssueDate.Add(new TimeSpan(timeout.Ticks / 2)); expirationDate.AddMinutes(1); } else expirationDate = originalTicket.Expiration; FormsAuthenticationTicket ft = new FormsAuthenticationTicket (originalTicket.Version, originalTicket.Name, originalTicket.IssueDate, expirationDate, originalTicket.IsPersistent, originalTicket.UserData, Request.QueryString[“CustomCookiePath”] ); string redirectUrl = Request.QueryString[“CustomReturnUrl”]; Response.Redirect( redirectUrl + “?” + FormsAuthentication.FormsCookieName + “=” + FormsAuthentication.Encrypt(ft)); } } VB.NET Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) _ Handles Me.Load ‘If the user is already authenticated, then punt them back ‘to the original application, but place a new forms authentication ‘ticket on the query string. If User.Identity.IsAuthenticated = True Then ‘This information comes from the forms authentication cookie for the ‘central login site. Dim fi As FormsIdentity = CType(User.Identity, FormsIdentity) Dim originalTicket As FormsAuthenticationTicket = fi.Ticket ‘For sliding expirations, ensure the ticket is periodically refreshed. Dim expirationDate As DateTime If FormsAuthentication.SlidingExpiration = True Then Dim timeout As TimeSpan = _ originalTicket.Expiration.Subtract(originalTicket.IssueDate) Download at Boykma.Com 353 Chapter 6: Forms Authentication expirationDate = _ originalTicket.IssueDate.Add(New TimeSpan(timeout.Ticks / 2)) expirationDate.AddMinutes(1) Else expirationDate = originalTicket.Expiration End If Dim ft As New FormsAuthenticationTicket( _ originalTicket.Version, originalTicket.Name, _ originalTicket.IssueDate, expirationDate, originalTicket.IsPersistent, _ originalTicket.UserData, Request.QueryString(“CustomCookiePath”)) Dim redirectUrl As String = Request.QueryString(“CustomReturnUrl”) Response.Redirect(redirectUrl & “?” & FormsAuthentication.FormsCookieName & “=” & FormsAuthentication.Encrypt(ft)) End If End Sub Actually, what happens when a website used first needs to login against the central login application is that the Load event handler runs. However, because this event handler falls through for unauthenticated users, the very first time a user needs to log in he or she instead ends up with the login page being rendered and can perform an interactive login. The noninteractive login occurs on most subsequent requests. For example, the button click handler for the login page redirects back to the same page. When the redirect comes back to the login page, there is now a master forms authentication ticket sent along with the request (from the SetAuthCookie call in the button click handler). As a result, when the Load event runs again, it sees that the user is authenticated, and so no interactive UI is even rendered. The Load event first gets a reference to the master forms authentication ticket because it needs most of the information in that ticket to create a forms authentication ticket for the participating site. The Load event creates a new forms authentication ticket and carries over almost all of the settings from the master forms authentication ticket. For example, this means a participating site gets the exact same issue date and expiration date as the master forms authentication ticket. If you build a similar solution, you could choose to actually store DateTime.Now for the IssueDate of the new ticket. The main point, though, is that the expiration date for tickets sent to participating sites is based on the expiration date for the login against the central login application. If you use absolute ticket expiration in the central login application, the behavior when tickets timeout in participating applications is pretty clear. When a forms authentication ticket times out in a participating application, the request is redirected through the local login page, which ends up requesting the central login page. However, because all tickets use the same timeout values, the master forms authentication ticket has also timed out. As a result, the redirect to the central login application falls through the Load event (the user is no longer considered authenticated), and instead the interactive login is shown. When the interactive login completes, a new master forms authentication ticket is issued, and the second execution of the login page results in a redirect with a new ticket and a new expiration date back to the participating application. On the other hand, if you use sliding expirations in the central login application, the reauthentication should be transparent to the website user. The ticket for the participating application is issued with a 354 Download at Boykma.Com Chapter 6: Forms Authentication modified expiration date. Instead of using the same expiration date as the master forms authentication ticket, the time to live for the ticket is set to half the TTL for the master forms authentication ticket, plus one extra minute. Because you know that forms authentication automatically reissues tickets when 50 percent or more of the remaining time to live has passed for a ticket, the idea is to create a ticket for the participating applications that will timeout in a similar manner. The extra one minute is added to account for clock-skew between the central login application and participating applications. What happens now is that in the participating applications with absolute expirations, the forms authentication ticket eventually times out at (IssueDate + 50 percent of the central login application’s timeout + 1 minute). This results in a redirect back to the central login page. However, because (Expiration​ Date - 50 percent of the central login application’s timeout - 1 minute) of time remains on the master forms authentication ticket, the master ticket is still considered valid. On the other hand, though, because the master forms authentication ticket has less than 50 pertcent of its remaining lifetime left, the FormsAuthenticationModule in the central login application will automatically renew the master forms authentication ticket, which results in a new IssueDate and a new ExpirationDate. Because the renewal occurs in the HTTP pipeline before the login page ever runs, by the time the Load event executes, a new master forms authentication ticket is available. As a result, the ticket that is created for the participating application contains a new IssueDate and an ExpirationDate roughly equal to (DateTime.Now + 50 percent of the central login application’s timeout + 1 minute). When this ticket is sent back to the participating application, it results in a valid forms authentication ticket, and the website user is returned to the originally requested page. Although a few redirects occurred underneath the hood, there was no interactive login required to renew the cookie. Another property in the new forms authentication ticket that differs is the CookiePath. Rather than cloning over the cookie path from the forms authentication ticket, the value from the CustomCookiePath query-string variable is used instead. This is how the central login application ensures that the ticket sent back to the participating application has the correct path information. The FormsAuthentication​ Module in the participating application will use the CookiePath value from this ticket when it constructs and issues the forms authentication cookie. The CustomReturnUrl query-string variable is used to build the redirect URL. Because this value includes the full qualified path back to a page in the participating application, the redirect issued by the central login page can cross servers and domains. You can see the chain that leads up to this point as well: 1. Participating application creates the fully qualified return URL. 2. Central login application replays fully qualified return URL when it redirects to itself. 3. Central login application uses replayed fully qualified return URL when it redirects back to the participating application. The actual redirect includes the query-string variable and value with the forms authentication ticket. It uses the exact same code as you saw earlier when cross-application redirects were first introduced. The Final Leg of the SSO Login At this point, a redirect has been issued back to the participating application, to the specific page that the website user was originally trying to access. The user is able to navigate around the participating application because now there is a valid forms authentication cookie. If the cookie eventually times out, the behavior described earlier around ExpirationDate takes effect, and a new ticket is issued. Download at Boykma.Com 355 Chapter 6: Forms Authentication If the website user surfs over to another participating application, there is of course no forms authentication cookie for this third application. However, the exact same logic applies. In the third application: 1. A redirect to the local login page occurs. 2. The local login page redirects to the central login application. 3. Because the master forms authentication ticket exists, the central login application transparently creates a new ticket and sends it back to the participating application. 4. The participating application converts the ticket in the query-string into a valid forms authenti- cation cookie, and the originally requested page runs. Examples of Using the SSO-Lite Solution Using a sample participating application called AppAUsingCentralLogin, the initial attempt to fetch default.aspx results in a redirect to the interactive login page in the central login application. The URL at this point looks like (bolded areas inserted for clarity): http://bhaidar-pc/CentralLogin/Login.aspx?CustomReturnUrl=http%3a%2f%2flocalhost%2f AppAUsingCentralLogin%2fDefault.aspx&CustomCookiePath=%2fAppAUsingCentralLogin You can see that the URL is pointed at the central login page. The CustomReturnUrl query-string variable contains the URL-encoded representation of a test server as well as the full path to default.aspx. The CustomCookiePath query-string variable contains the path information that was set in the configuration element of the participating application /AppAUsingCentralLogin. After successfully logging in, you are redirected back to the originally requested URL. The URL in the address bar at this point looks like: http://localhost/AppAUsingCentralLogin/Default.aspx?.ASPXAUTH=090AC8BAD650B5186DD7B A78D7A5A88F310F2C69CCD6C640C2541AC1CF2559F6D8283EC9339A957B8005CEB6C8306715471654A8 53E33BD57859C0BFED309DBDC08C582A0FDBBB3C7E0B5993A23E8BBD2BD8ACBC6ABC04607A423067273 F4A83112C5F52679FA71AB36D5F8144BB20586832623F6BB17EC1 Because the SSO-lite solution relies on cross-application redirects, the very first page accessed after the redirect from the central login application includes the forms authentication ticket sitting in the querystring. If you navigate around into the site though, this query-string variable goes away: http://localhost/AppAUsingCentralLogin/AnotherPage.aspx If you now navigate over to a second participating application: http://bhaidar-PC/AppBUsingCentralLogin/Default.aspx There is a slight pause while the redirects occur, but you end up on default.aspx, with the address bar showing the following: http://localhost/AppBUsingCentralLogin/Default.aspx?.ASPXAUTH=079B144714F15D4934761 64AC79DBE45D91DD19A1DB728F591CFB9B08307E4B0ECCE05E4A4DE5F62E997F4521477F1B3C9FD7A31 A8F25387BE18A64E1B50954C126353791741AC698165140E4C71A12D31A9E22F0AC8BD425D026F6A800 5B5028D039253F66A23AB97DED3F1DB3D9009B691C615B77BAE20 356 Download at Boykma.Com Chapter 6: Forms Authentication No prompt for login occurs, though, because the master forms authentication cookie has already been issued. As with the first participating application, the initial redirect from the central login application back to application B (in this case), results in the forms authentication ticket showing on the URL. When you navigate deeper into the site, this will go away. Although I can’t show it here in a book, if you take the code for the central login application in Visual Studio and attach to w3wp.exe with the debugger, you can see how tickets are renewed in the sliding expiration case with the following steps: 1. Set the timeout attribute in the central login application to three minutes or more. 2. Access one of the participating applications and go through the login process. 3. Attach the central login application with the debugger and set breakpoints in the Load event of the login page. 4. Wait for 2.5 minutes (50 percent of the central application’s timeout plus one minute). This is the timeout on the ticket sent to the participating application. 5. Access another page in the participating application. At this point, you will see that the break- points in the central login page are hit and a new forms authentication ticket is issued for the participating application. If you inspect the new IssueDate and ExpirationDate, you will see that they have all been updated with new values. Because the master forms authentication ticket was 2.5 minutes old when the redirect back to the central login application occurred, the FormsAuthenticationModule in the central login application automatically renewed the master ticket as well. Final Notes on the SSO-Lite Solution You have seen that with cross-application redirects in ASP.NET 2.0 and ASP.NET 3.5’s forms authentication, it is possible to sort of cobble together an SSO-like solution. However, now that I have shown how to accomplish it, there are a number of technical points that you still need to keep in mind. ❑❑ The solution depends entirely on redirects between different servers and different domains. There may be the possibility of getting browser security warnings when running under SSL and a redirect occurs to a completely different application and DNS domain. ❑❑ Because of the dependency on redirects, you need to be careful in how participating applications are structured as well as in the ticket timeouts. It is entirely possible that a user working on a form in an application posts data back to the server and then loses all of the information when a silent reauthentication with the central login site occurs. ❑❑ In the case of sliding expirations, the sample depends on very specific behavior around the renewal of forms authentication tickets. Although this renewal behavior is documented, the trick with adding a one minute offset is fragile, both due to the potential for changes in the underlying forms authentication behavior as well as the variability around clock skew between participating applications and the central login server. A more robust solution could involve a custom HttpModule installed on each participating site that would optionally renew the ticket based on information carried in the UserData property of the ticket. ❑❑ You may want more control over how ticket timeouts are handled in general, both for the master forms authentication ticket and for the participating sites. For example, you may want configurable ticket timeouts that vary depending on which participating application is requesting a ticket. Download at Boykma.Com 357 Chapter 6: Forms Authentication ❑❑ There was no concept of federation or trust shown in the sample SSO solution. For an in-house IT shop, this probably would not be an issue because developers at least know of other development organizations sharing server farms and there is an implicit level of trust. However, in the case of disparate Internet facing sites run by different companies, trust is an incredibly important aspect of any SSO solution. Attempting to create an SSO solution on top of forms authentication for such a scenario probably isn’t realistic. ❑❑ Last, the sample application allows any participating application to make use of it. With the prevalence of phishing attacks on the Internet these days, you would want to add some additional security in an SSO-lite solution. At a minimum, you would want the central login application to only accept login attempts from URLs that are “trusted” by the central login application. This would prevent attacks where a malicious website poses as the login page to a legitimate site, and then through social engineering attacks (that is, an unwary user clicking through a spam email) harvests a valid forms authentication ticket issued by the central login application. This specific scenario is why, for more complex SSO scenarios, you would want to use a commercial SSO product that incorporates the concept of trust, both trust between participating sites as well as trust between applications and the website that issues credentials. Overall, I think these points highlight the fact that cross-application redirects can definitely be used for solving some of the simpler problems companies run into around single sign-on. However, if you find that your websites require more than just a basic capability to share tickets across servers and applications, you will probably need to either write more code to handle your requirements or go with a thirdparty SSO solution. Enforcing Single Logons and Logouts A question that comes up from time to time is the desire to ensure the following behavior when users login with forms authentication: ❑❑ Users should be allowed to login once, and only once. If they attempt to log in a second time in an application, the login should be rejected. ❑❑ If users explicitly log out, the fact that they logged out should in some way be remembered to prevent replaying previous authentication tickets. Both of these design questions highlight the fact that forms authentication is a lightweight mechanism for enforcing authentication. Forms authentication as a feature does not have any back-end data store. As a result there isn’t an out-of-box solution that automatically keeps track of login sessions and subsequent logouts. However, with a little bit of coding, it is possible to deal with both scenarios in ASP.NET 2.0 and ASP.NET 3.5. The solution outlined in this section relies on the Membership feature of ASP.NET 2.0 and ASP.NET 3.5. There is an extensive discussion of extending Membership in Chapters 11, 12, and 13. However, because this chapter deals with forms authentication, it makes more sense to show the Membership-based solution at this point rather than deferring it. Because Membership is designed to work hand-in-hand with forms authentication, it is a logical place to store “interesting” information about the logged-in or logged-out state of a user account. Of course, you could write your own database solution for the same purposes, or possibly even use the Profile feature in ASP.NET 2.0 and ASP.NET 3.5 for similar purposes, 358 Download at Boykma.Com Chapter 6: Forms Authentication but given that Membership is readily available and is part of the authentication stack in ASP.NET 2.0 and ASP.NET 3.5, it makes sense to leverage it. Enforcing a Single Logon For the first scenario of preventing duplicate login attempts, the fact that Membership stores its information in a database (or in AD and ADAM, if you so choose) makes it very useful in web farms. Any information stored into the MembershipUser instance for a logged-on user will be available from any other web server in the farm. In the same vein, because Membership providers can be configured in multiple applications to point at the same database, it is also possible to use information in a MembershipUser instance across multiple applications. The MembershipUser object doesn’t have many places for storing additional information. However, the Comment property on MembershipUser is not used by ASP.NET, so it is a convenient place to store information without needing to write derived versions of MembershipUser as well as derived versions of MembershipProvider(s). Enforcing the concept of a single logon requires tracking two pieces of information associated with a successful logon: ❑❑ The expiration time for the successful logon ❑❑ Some type of identifier associated with the logon Knowing when a successful logon expires is important because most website users probably never use explicit logout mechanisms. Instead, most users navigate through a site, perform whatever required work there is and then close the browser. In this case, if a user comes back to the site at a later point after the original logon session has expired, you do not want to nag the user about preexisting logon sessions that have since expired. Instead, you want an authentication solution that recognizes the previous logon has expired and silently cleans up after the fact. The second piece of information is important to keep track of because you need some concrete representation of the fact that a user logged in to the website. Just storing an expiration date is not sufficient. An expiration date indicates when an active logon session expires, but the date alone does not give you enough information to correlate to the fact that someone logged in to a website. By tracking some type of session identifier, you can check on each inbound request whether the authentication data is for the active logon session or for some other logon session. A logon session identifier also gives the website user the ability to forcibly logout another active session. This scenario is important if, for example, a user logs in to your website on one machine and forgets about it. Then the user walks down the hallway to another machine and attempts to log in again. With the logon session identifier, you have a way to allow the user to log on using other machines while ensuring that the previous logon session (or sessions) that are sitting idle on some other machine cannot be reused when the individual gets back to his or her desk. So, just from this brief overview of the main problems involved with enforcing a single login, you can see that there is a fair amount of tracking and enforcement necessary to get all this working. The good thing, though, is that it is possible to build this type of enforcement using the existing forms authentication and Membership features. Download at Boykma.Com 359 Chapter 6: Forms Authentication You will start out building the solution by looking at a sample login page. Since ASP.NET 2.0 and ASP. NET 3.5 conveniently include the UI login controls, building the basic UI with logical events during the login process is a snap. Drop a login control onto a page, and then convert it into a template. Converting it into a template allows you to add UI customizations as needed. In this case, you need to add a check box that allows an end user to forcibly logout other active logon sessions. So much for the UI aspect of the login control. Switching to the code-behind for the page, there are two events that you want to handle: ❑❑ LoggingIn: This event gives you the opportunity to perform some checks before the Login control attempts to validate credentials using the Membership feature. It is a good place to check and see whether or not another active logon session is in progress. ❑❑ LoggedIn: This event occurs after the Login control has successfully validated credentials. Because enforcing a single login requires some extra work on your part, this is the logical point to create a FormsAuthenticationTicket with extra information and issue it. The LoggedIn event is where you store information inside of Membership that indicates the logon session ID as well as the session expiration inside of the forms authentication ticket. C# //snip… protected MembershipUser loginUser; protected void Login1_LoggedIn(object sender, EventArgs e) { if (loginUser == null) loginUser = Membership.GetUser(Login1.UserName); //represents the active login “session” Guid g = System.Guid.NewGuid(); HttpCookie c = Response.Cookies[FormsAuthentication.FormsCookieName]; FormsAuthenticationTicket ft = FormsAuthentication.Decrypt(c.Value); //Generate a new ticket that includes the login session ID FormsAuthenticationTicket ftNew = new FormsAuthenticationTicket( ft.Version, ft.Name, ft.IssueDate, ft.Expiration, ft.IsPersistent, 360 Download at Boykma.Com Chapter 6: Forms Authentication g.ToString(), ft.CookiePath); //Store the expiration date and login session ID in Membership loginUser.Comment = “LoginExpiration;” + ft.Expiration.ToString() + “|LoginSessionID;” + g.ToString(); Membership.UpdateUser(loginUser); //Re-issue the updated forms authentication ticket Response.Cookies.Remove(FormsAuthentication.FormsCookieName); //Basically clone the original cookie except for the payload HttpCookie newAuthCookie = new HttpCookie( FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ftNew)); //Re-use the cookie settings from forms authentication newAuthCookie.HttpOnly = c.HttpOnly; newAuthCookie.Path = c.Path; newAuthCookie.Secure = c.Secure; newAuthCookie.Domain = c.Domain; newAuthCookie.Expires = c.Expires; //And set it back in the response Response.Cookies.Add(newAuthCookie); } VB.NET … Protected loginUser As MembershipUser Protected Sub Login1_LoggedIn( _ ByVal sender As Object, _ ByVal e As EventArgs) Handles Login1.LoggedIn If loginUser Is Nothing Then loginUser = Membership.GetUser(Login1.UserName) End If ‘DetermineExpirationForNewLogin(); ‘represents the active login “session” Dim g As Guid = System.Guid.NewGuid() Dim c As HttpCookie = Response.Cookies(FormsAuthentication.FormsCookieName) Dim ft As FormsAuthenticationTicket = _ FormsAuthentication.Decrypt(c.Value.ToString) ‘Generate a new ticket that includes the login session ID Dim ftNew As New FormsAuthenticationTicket( _ ft.Version, ft.Name, ft.IssueDate, ft.Expiration, _ ft.IsPersistent, g.ToString(), ft.CookiePath) Download at Boykma.Com 361 Chapter 6: Forms Authentication ‘Store the expiration date and login session ID in Membership loginUser.Comment = “LoginExpiration;” & ft.Expiration.ToString() & “|LoginSessionID;” & g.ToString() Membership.UpdateUser(loginUser) ‘Re-issue the updated forms authentication ticket Response.Cookies.Remove(FormsAuthentication.FormsCookieName) ‘Basically clone the original cookie except for the payload Dim newAuthCookie As New HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ftNew)) newAuthCookie.HttpOnly = c.HttpOnly newAuthCookie.Path = c.Path newAuthCookie.Secure = c.Secure newAuthCookie.Domain = c.Domain newAuthCookie.Expires = c.Expires ‘And set it back in the response Response.Cookies.Add(newAuthCookie) End Sub After a successful login, the page first ensures there is a MembershipUser reference available for the user that is logging in. The GetUser(…) overload that accepts a username must be used because even though the user’s credentials have been successfully verified at this point, from a forms authentication viewpoint, the page is still running with an anonymous user on the current HttpContext. It won’t be until the next page request that the FormsAuthenticationModule has a cookie on the request that it can convert into a FormsIdentity. Because the LoggedIn event won’t run unless other preliminary checks ensure that it is alright for the user to login, there aren’t any other validation checks in this event handler. To reach this event, the credentials will already have been verified as matching, and the other checks in the LoggingIn event (shown a little bit later) will also have been passed. For this sample, a Guid was chosen as the representation of a login session—so the event handler creates a new Guid to represent a new instance of a login session. As you have seen in other sections, because the forms authentication APIs do not expose timeout information, you need to get to it through a workaround. In this case, because the Login control has already called SetAuthCookie internally, there is a valid forms authentication cookie sitting in the Response. With this cookie, you can get the FormsAuthenticationTicket for the user that is logging in. A new FormsAuthenticationTicket is created that is a clone of the already issued ticket, with one difference. The UserData information in the ticket is where the Guid login session identifier is stored. Note that because this sample application relies on the UserData property, enforcing a single logon in this manner will only work with clients that support cookies. The Expiration and the Guid for the ticket are also packaged up and stored in the MembershipUser instance for the user logging in. In more complex applications, you could create a custom class that represented this type of information, run the class through the XmlSerializer, and store the output in the Comment property. For simplicity though, the sample application stores the information with the following format: LoginExpiration;expiration_date|LoginSessionID;the_Guid 362 Download at Boykma.Com Chapter 6: Forms Authentication Each piece of information is a name-value pair, with different name-value pairs delimited with the pipe character. Within a name-value pair, the two pieces of information are delimited by a semicolon. Once the Comment field has the new information, Membership.UpdateUser is called to store the changes back to the database. The last piece of work during login is to replace the forms authentication cookie issued by the Login control with the FormsAuthenticationTicket that has the UserData in it. Again, rather than attempting to hard-code pieces of forms authentication configuration information into the application, the sample code simply reuses all of the settings from the Login control’s cookie to create a new cookie with all of the correct settings. The Login control’s original cookie is then removed from the Response, and the new cookie is added in its place. At this point, when the login page completes, the user is successfully logged in with the session identifier flowing back and forth between the browser and the web server inside of the forms authentication ticket. There is also a persistent representation of the expiration time for the login as well as the session identifier stored in the Membership system. These pieces of information form the basis for checking the validity of a login on each and every request. Because the FormsAuthenticationModule runs during the AuthenticateRequest event in the pipeline, it makes sense to perform additional validations after forms authentication has performed the basic work of determining whether or not there is a valid forms-authenticated user for the request. A custom HttpModule is used to enforce that the current request is associated with the current login session. C# public class FormsAuthSessionEnforcement : IHttpModule { public FormsAuthSessionEnforcement(){} public void Dispose() {} public void Init(HttpApplication context) { context.PostAuthenticateRequest += new EventHandler(OnPostAuthenticate); } private void OnPostAuthenticate(Object sender, EventArgs e) { HttpApplication a = (HttpApplication)sender; HttpContext c = a.Context; //If the user was authenticated with Forms Authentication //Then check the session ID. if (c.User.Identity.IsAuthenticated == true) { FormsAuthenticationTicket ft = ((FormsIdentity)c.User.Identity).Ticket; Guid g = new Guid(ft.UserData); MembershipUser loginUser = Membership.GetUser(ft.Name); string currentSessionString = Download at Boykma.Com 363 Chapter 6: Forms Authentication loginUser.Comment.Split(“|”.ToCharArray())[1]; Guid currentSession = new Guid(currentSessionString.Split(“;”.ToCharArray())[1]); //If the session in the cookie does not match the current session as // stored in the Membership database, then terminate this request if (g != currentSession) { FormsAuthentication.SignOut(); FormsAuthentication.RedirectToLoginPage(); } } } } VB.NET Public Class FormsAuthSessionEnforcement Implements IHttpModule Public Sub New() End Sub Public Sub Init(ByVal context As HttpApplication) Implements IHttpModule.Init AddHandler context.PostAuthenticateRequest, AddressOf OnPostAuthenticate End Sub Private Sub OnPostAuthenticate(ByVal sender As Object, ByVal e As EventArgs) Dim a As HttpApplication = CType(sender, HttpApplication) Dim c As HttpContext = a.Context If c.User Is Nothing Then Return End If ‘If the user was authenticated with Forms Authentication ‘Then check the session ID. If c.User.Identity.IsAuthenticated = True Then Dim ft As FormsAuthenticationTicket = _ (CType(c.User.Identity, FormsIdentity)).Ticket Dim g As New Guid(ft.UserData) Dim loginUser As MembershipUser = Membership.GetUser(ft.Name) Dim currentSession As Guid ‘If there isn’t any session information in Membership at this point ‘then it is likely the user logged out, and an old cookie is ‘being replayed. If (Not String.IsNullOrEmpty(loginUser.Comment)) Then Dim currentSessionString As String = _ loginUser.Comment.Split(“|”.ToCharArray())(1) currentSession = _ New Guid(currentSessionString.Split(“;”.ToCharArray())(1)) Else currentSession = Guid.Empty End If 364 Download at Boykma.Com Chapter 6: Forms Authentication ‘If the session in the cookie does not match the current session as ‘stored in the Membership database, then terminate this request If g <> currentSession Then FormsAuthentication.SignOut() FormsAuthentication.RedirectToLoginPage() End If End If End Sub Public Sub Dispose() Implements IHttpModule.Dispose End Sub End Class The custom module hooks the PostAuthenticateRequest event so that it can inspect the authenticated credentials after the FormsAuthenticationModule has run. If the current request doesn’t have an authenticated user, the module exits. On the other hand, if there is an authenticated user, the module gets a reference to the FormsAuthenticationTicket and extracts the Guid login session identifier. The login information for the authenticated user is also retrieved from the Membership database. The module is only concerned with checking the validity of the session identifier so that it doesn’t bother retrieving the expiration date from the MembershipUser instance because the FormsAuthentication​ Module will already have made this check. The module does check the session identifier in the ticket against the session identifier stored in the database. If they match, the request is allowed to proceed. However, if the two identifiers do not match, this is an indication that the current request is not associated with an active and valid login session. In this case, the module calls FormsAuthentication.SignOut, which has the effect of issuing a cookie that will clear the forms authentication cookie in the browser. Then the module redirects the current request to the login page for the application. Because all this logic is encapsulated in an HttpModule, the module needs to be registered in each application that wants to make use of its services. In terms of code deployment, for the sample application the code is in the App_Code directory; although again you can instead choose to author it in a separate assembly deployed in the bin or the GAC. Depending on how the module is deployed, you will need to add more information to the type attribute. Note that the sample code shown here only includes checks that make sense in the case of absolute ticket expirations. The custom module and login page do not handle the case where sliding expirations are enabled. You would need extra logic to periodically update the expiration data in the Membership database whenever the FormsAuthenticationModule renewed the ticket. As a result, the configuration for the sample application only allows absolute expirations. Download at Boykma.Com 365 Chapter 6: Forms Authentication When the module exits, one of two outcomes has occurred: either the login session is valid and the request continues, or the session is invalid and the user is prompted to log in again. Assuming that the user is prompted for a login, this brings us full circle back to the login page. As shown earlier, there is a check box on the login page that allows a user to clear active login sessions. The setting of this check box, as well as the logic to prevent duplicate logins, is in the LoggingIn event of the Login control. C# protected void Login1_LoggingIn(object sender, LoginCancelEventArgs e) { if (loginUser == null) loginUser = Membership.GetUser(Login1.UserName); //See if the user indicates that they want an existing login session //to be forcibly terminated CheckBox cb = (CheckBox)Login1.FindControl(“ForceLogout”); if (cb.Checked) { loginUser.Comment = String.Empty; Membership.UpdateUser(loginUser); return; } //Only need to check if the user instance already has login information //stored in the Comment field. if ((!String.IsNullOrEmpty(loginUser.Comment)) && loginUser.Comment.Contains(“LoginExpiration”)) { string currentExpirationString = loginUser.Comment.Split(“|”.ToCharArray())[0]; DateTime currentExpiration = DateTime.Parse((currentExpirationString.Split(“;”.ToCharArray()))[1]); //The user was logged in at some point previously and the login is //still valid if (DateTime.Now <= currentExpiration) { e.Cancel = true; Literal tx = (Literal)Login1.FindControl(“FailureText”); tx.Text = “You are already logged in.”; } } } VB.NET Protected Sub Login1_LoggingIn( _ ByVal sender As Object, _ ByVal e As LoginCancelEventArgs) _ Handles Login1.LoggingIn If loginUser Is Nothing Then loginUser = Membership.GetUser(Login1.UserName) End If 366 Download at Boykma.Com Chapter 6: Forms Authentication ‘See if the user indicates that they want an existing login session ‘to be forcibly terminated Dim cb As CheckBox = CType(Login1.FindControl(“ForceLogout”), CheckBox) If cb.Checked Then loginUser.Comment = String.Empty Membership.UpdateUser(loginUser) Return End If ‘Only need to check if the user instance already has login information ‘stored in the Comment field. If ((Not String.IsNullOrEmpty(loginUser.Comment))) AndAlso _ loginUser.Comment.Contains(“LoginExpiration”) Then Dim currentExpirationString As String = _ loginUser.Comment.Split(“|”.ToCharArray())(0) Dim currentExpiration As DateTime = _ DateTime.Parse((currentExpirationString.Split(“;”.ToCharArray()))(1)) ‘The user was logged in at some point previously and the login is still ‘valid If DateTime.Now <= currentExpiration Then e.Cancel = True Dim tx As Literal = _ CType(Login1.FindControl(“FailureText”), Literal) tx.Text = “You are already logged in.” End If End If End Sub Duplicate login checks always require a MembershipUser to be handy, so the event first ensures that an instance is available. Because the LoggingIn event is always fired by the Login control before the LoggedIn event, the check that is made in the LoggedIn event will always find a MembershipUser instance already available for use. If the check box is selected (that is, the website user indicated that they want any active login session to be invalidated), the session information inside of the MembershipUser instance is cleared and the information is saved back to the Membership database. In essence, a setting of String.Empty in the MembershipUser.Comment field is an indication that the user is not logged in. One side note: Placing the check box on the Login control required converting the control into a template. Template editing mode for the control allows you to add arbitrary controls to the layout. However, there is not a convenient strongly typed reference to any controls that you add (hence the need for calling FindControl to get a reference to the check box). If there is login information contained in the Comment property, then the expiration date is extracted. From this, you can see that there are two different points in the application where expiration date and session identifiers are checked. The login session identifier is checked after the user is logged in. The expiration date is checked before the user is logged in. If the expiration date from the MembershipUser instance indicates that there is still a valid login session (that is, there is a session that will expire sometime in the future), the remainder of the processing of the Login control is halted by setting the Cancel property on the event arguments to true. A reference to the Literal control that displays error text is found, and appropriate error information is displayed to the user. Download at Boykma.Com 367 Chapter 6: Forms Authentication Each time a user logs in there are a few possible decision trees that will occur on the Login page: 1. The user is logging in for the very first time to the application. As a result, all the checks in the LoggingIn event are bypassed, and a login occurs. 2. The user is logging in after a previous login session already expired. In this case, the expiration date check in the LoggingIn event detects this, and the user is allowed to log in. 3. The user is logging in, but there is already a valid login session as indicated by the expiration date information within the Comment field. In this case, the login is not allowed to proceed and an error is returned. 4. The user is logging in and explicitly states that any previous session should be invalidated. This is similar to the first point, with some extra work performed to clear the Comment field prior to allowing the login to proceed. You can try all this out by stepping through the process of logging in multiple times: 1. If you don’t already have a user, you can quickly create one by using the ASP.NET Configura- tion tool inside of Visual Studio (Website ➪ ASP.NET Configuration Tool). 2. Log in with a user to the sample site. If you look in the database, you will see login information inside of the Comment column of the aspnet_Membership database table. The data looks like: LoginExpiration;5/22/2005 12:52:51 PM|LoginSessionID;71fa38d5-97f8-4c628bbb-bac4ab2f352b. 3. Open up a second browser window and type the address of a secured page in the application. This will require you to log in again. 4. Note that when you attempt to log in in the second browser instance, the login fails because of the checks being made in the LoggingIn event on the login page. 5. Now attempt to log in but make sure to click the check box to invalidate other login sessions. You will be able to log in at this point successfully. If you check the Comment column in the database, you will see updated information there. 6. Flip back to the first browser window and attempt to continue navigating around the site. You will instead get redirected back to the login page because of the login session ID check being made by the custom HttpModule. The module detects the login session in the first browser is no longer the active login session. Enforcing a Logout An issue related to the single login scenario is the potential for a user to reenter the site as a logged-in user after he or she has already logged out. If this sounds a bit strange, the following sequence of events can lead to this: 1. The user logs in and gets back a valid forms authentication ticket. 2. At some point in the future, the authentication ticket is hijacked or exposed. 368 Download at Boykma.Com Chapter 6: Forms Authentication 3. The user logs out, thus clearing the forms authentication cookie from his or her browser. 4. The malicious individual from step 2 replays the ticket back to the site. Assuming that the expi- ration date in the ticket is still valid, the malicious user can now run as an authenticated user. In reality, the possibility of step 2 is open to quite a bit of debate. If you run your entire site under SSL (or at the very least set requireSSL to true in configuration), hijacking a forms authentication from a network trace is not possible. Prior to ASP.NET 2.0, though, it was still possible to use some type of cross-site scripting attack to hijack a cookie using client-side browser code. However, in ASP.Net 2.0 and ASP.NET 3.5 the HttpOnly property of forms authentication cookies is set to true, so this attack vector is quite a bit harder to accomplish (though, as noted earlier, it may be possible to use the TRACE/TRACK command which, if supported on the web server, still allows access to the cookie). Furthermore, there isn’t anything in the steps listed earlier that would prevent this type of replay attack from occurring with a technically savvy user that sits down at a coworker’s machine and attempts to physically copy a cookie and email it back to himself (though even this attack would be partially mitigated by using only session based cookies). Anyway, the point here is that for high-security sites, you don’t want to allow theoretical vulnerabilities, especially if there are reasonable steps that you can take to prevent the problem in the first place. Because you have already seen the solution for preventing multiple logins, it is pretty easy to extend it one step further. A value of String.Empty in the MembershipUser.Comment field is already treated as an indicator that there is no active login session. If you add a LoginStatus control to the pages in your site, you can hook the LoggingOut event and perform some extra cleanup. C# protected void LoginStatus1_LoggingOut(object sender, LoginCancelEventArgs e) { //Clear the information in Membership that tracks the //the current login session. MembershipUser mu = Membership.GetUser(); mu.Comment = String.Empty; Membership.UpdateUser(mu); } VB.NET Protected Sub LoginStatus1_LoggingOut( _ ByVal sender As Object, _ ByVal e As LoginCancelEventArgs) _ Handles LoginStatus1.LoggedOut ‘Clear the information in Membership that tracks the ‘the current login session. Dim mu As MembershipUser = Membership.GetUser() mu.Comment = String.Empty Membership.UpdateUser(mu) End Sub Download at Boykma.Com 369 Chapter 6: Forms Authentication Now whenever a website user explicitly logs out of a site, the login information for that user is deleted from the user record in the Membership database. With this change, there is one extra modification needed in the custom HttpModule as well. C# private void OnPostAuthenticate(Object sender, EventArgs e) { HttpApplication a = (HttpApplication)sender; HttpContext c = a.Context; //If the user was authenticated with Forms Authentication //Then check the session ID. if (c.User.Identity.IsAuthenticated == true) { FormsAuthenticationTicket ft = ((FormsIdentity)c.User.Identity).Ticket; Guid g = new Guid(ft.UserData); MembershipUser loginUser = Membership.GetUser(ft.Name); Guid currentSession; //If there isn’t any session information in Membership at this point //then it is likely the user logged out, and an old cookie is //being replayed. if (!String.IsNullOrEmpty(loginUser.Comment)) { string currentSessionString = loginUser.Comment.Split(“|”.ToCharArray())[1]; currentSession = new Guid(currentSessionString.Split(“;”.ToCharArray())[1]); } else currentSession = Guid.Empty; //If the session in the cookie does not match the current session as // stored in the Membership database, then terminate this request if (g != currentSession) { FormsAuthentication.SignOut(); FormsAuthentication.RedirectToLoginPage(); } } VB.NET Private Sub OnPostAuthenticate(ByVal sender As Object, ByVal e As EventArgs) Dim a As HttpApplication = CType(sender, HttpApplication) Dim c As HttpContext = a.Context If c.User Is Nothing Then Return End If 370 Download at Boykma.Com Chapter 6: Forms Authentication ‘If the user was authenticated with Forms Authentication ‘Then check the session ID. If c.User.Identity.IsAuthenticated = True Then Dim ft As FormsAuthenticationTicket = _ (CType(c.User.Identity, FormsIdentity)).Ticket Dim g As New Guid(ft.UserData) Dim loginUser As MembershipUser = Membership.GetUser(ft.Name) Dim currentSession As Guid ‘If there isn’t any session information in Membership at this point ‘then it is likely the user logged out, and an old cookie is ‘being replayed. If (Not String.IsNullOrEmpty(loginUser.Comment)) Then Dim currentSessionString As String = _ loginUser.Comment.Split(“|”.ToCharArray())(1) currentSession = _ New Guid(currentSessionString.Split(“;”.ToCharArray())(1)) Else currentSession = Guid.Empty End If ‘If the session in the cookie does not match the current session as ‘stored in the Membership database, then terminate this request If g <> currentSession Then FormsAuthentication.SignOut() FormsAuthentication.RedirectToLoginPage() End If End If End Sub The bolded section shows the changes to the module. Instead of just assuming that there will always be a value in the Comment property for the authenticated user, the module instead checks to see if the Comment property has any valid information in it. If there is no information in the Comment property, then the comparison between the session identifier in the forms authentication ticket and the value Guid.Empty always fails. If a malicious user attempts to replay an otherwise valid forms authentication cookie, and the true user logged out of the application, the replayed ticket will never be accepted. Looking at this code, you can see why for very secure sites, sliding expirations should never be used. Although you now have sample code that keeps track of the logged-in versus logged-out status of a user, there really isn’t much you can do to force a user to actually log out. How many of us just close down the browser when we are done with a site? In cases like this, the only remaining protection is for the forms authentication ticket to eventually expire. At least with absolute expirations the window of opportunity for a successful replay attack can be substantially narrowed. With sliding expirations, as long as a valid ticket is replayed to the site, the ticket will continue to work and will be periodically updated as well. Download at Boykma.Com 371 Chapter 6: Forms Authentication Summary Out of the box, forms authentication in ASP.NET 2.0 and ASP.NET 3.5 adds new protections by including the HttpOnly attribute on all forms authentication cookies. Used in conjunction with encryption and signing of the forms authentication ticket, the requireSSL attribute and absolute ticket expirations, you can quickly restrict the ability of malicious users to gain access to a forms authentication cookie. When running an application in the new IIS 7.0 integrated mode, you can enable the managed FormsAuthenticationModule to authenticate ASP.NET and non-ASP.NET resources. This comes as a result of having ASP.NET access to all request types when running under the integrated mode in IIS 7.0. ASP.NET 2.0 and ASP.NET 3.5 also include a cookieless mode of operation, whereby the forms authentication ticket is embedded in the URL. This makes it much easier for developers to author sites that work with mobile browsers as well as standard desktop browsers. In the interest of security, though, developers should avoid cookieless forms authentication tickets for sites that require high degrees of security. It is simply too easy to “leak” or expose a cookieless forms authentication ticket to someone other than the original user. Although forms authentication seems pretty simple, with a bit of custom code, you can actually solve some rather complex authentication problems. The ability in ASP.NET 2.0 and ASP.NET 3.5 to pass forms authentication tickets across applications makes it possible to solve some single sign-on issues that previously required complex third-party SSO applications. Of course, there is also a limit to how far you can stretch the new cross-application capabilities of forms authentication. For many developers, commercial SSO solutions will still make sense. The combination of forms authentication and Membership finally gives developers the basic plumbing needed to solve the single-logon problem. Although neither feature includes support for enforcing single-logons, both features are sufficiently extensible that with a reasonable amount of custom code you can prevent users from performing multiple logons. You can also provide protection so that when a user explicitly signs out, cookie replay attacks with a forms authentication cookie are not allowed. 372 Download at Boykma.Com 7 Integrating ASP.NET Security with Classic ASP All the great security features in ASP.NET do not really help you when you look at your older classic ASP applications. Although forms authentication and URL authorization have been around since the ASP.NET 1.0 days, these features have not been of any use in the ASP world. With the introduction of the Membership and Role Manager features in ASP.NET 2.0, you had even more authentication and authorization functionality built into ASP.NET, which ASP.NET 3.5 continues to support. But again, it seems like that functionality is orphaned in the ASP.NET world and never made it over to the world of classic ASP. Why attempt to bring the ASP.NET and classic ASP worlds together? In terms of sheer volume of code written, the majority of web applications out there are still running on classic ASP. Even if you surf around Microsoft’s own sites, such as the MSDN online library and various links and subsites of www.microsoft.com, you still encounter a lot of classic ASP pages. In ASP.NET 2.0 a number of small changes were made in some admittedly esoteric aspects of the runtime to make it possible to more tightly integrate ASP.NET and classic ASP. These changes also rely on modifications made earlier to IIS 6 around handling for ISAPI extensions. Both of these changes taken together make it possible to wrap classic ASP sites inside of ASP.NET. With the release of ASP.NET 3.5 and IIS 7.0, the integration between ASP.NET and classic ASP is made even easier, especially with IIS 7.0’s new integrated mode. As you know, IIS 7.0 provides two main modes of executing: the classic mode, which resembles that of IIS 6, and, integrated mode, which is new and unites the ASP.NET and IIS request pipeline into a single, integrated request pipeline. Download at Boykma.Com Chapter 7: Integrating ASP.NET Security with Classic ASP This chapter covers the following topics: ❑❑ ISAPI extension mapping behavior in IIS 5. ❑❑ Wildcard mappings in IIS 7.0 and how they work. ❑❑ The DefaultHttpHandler in ASP.NET 3.5. ❑❑ Using the DefaultHttpHandler with ASP.NET and classic ASP. ❑❑ Authenticating classic ASP using ASP.NE