Ceptor Agent for .NET
Introduction
Ceptor .NET Agent integrates Ceptor with .NET Applications or ASP.NET IIS web applications.
Ceptor .NET Agent facilitates use of single sign-on between both Java and .NET Applications.
Using Ceptor Owin you can also use Owin and ClaimsIdentity.
Requirements
Ceptor .NET Agent requires .NET v2.0 or newer. In addition, it uses the log4net framework (freely available from http://logging.apache.org/log4net/ ) and the log4net.dll can be found in the Ceptor Distribution.
Ceptor Owin requires .NET v4.5 or newer.
Installation
Installation of Ceptor .NET Agent is fairly simple, you just need to copy PortalProtect.Agent.dll
and log4net.dll
to your Global Assembly Cache – usually located in c:\windows\assembly
, this makes Ceptor .NET Agent available to all other programs on your machine.
To install Ceptor Owin, you need to install Ceptor.Owin.dll
in the GAC as well.
You can also use gacutil.exe (available in Microsoft SDK) to install the DLLs.
When upgrading Ceptor, be sure to uninstall older versions – also if your configuration specifies the exact version number to use as in this example:
<add name="PPAuth" type="PortalProtect.Providers.PPBasicAuthenticationModule, PortalProtect.Agent, Version=1.0.0.0, Culture=neutral, PublicKeyToken=218ecb9b9450d01f" />
Then you need to make sure to change the version number to the newly installed Ceptor.NET Agent version.
Configuration
Before using Ceptor .NET Agent, you need to configure it for each application – configuration depends on how you use it. If you use Ceptor .NET from within your own standalone application, you need to configure it in app.config
and if you use IIS, you will need to configure it in web.config
. You also have the option of setting the configuration programmatically.
Ceptor .NET Agent requires (just as its java counterpart) the name of itself, and the address of one or more configuration servers from where to retrieve its configuration.
In app.config or web.config, this is specified as follows:
And in web.config, this is specified like this:
<appSettings> <add key="servername" value="webserver1" /> <add key="cfgservers" value="tcp://localhost:21233" /> <add key="nowait" value="true" /> </appSettings>
In both cases, the name of the server itself, is webserver1, and the address of the configuration server is tcp://localhost:21233 – more servers can be added, separated by semicolon.
The setting nowait is optional, but if specified it tells the agent not to wait for a connection to the config server and session controller, but instead fail immediately if no connection is present.
Note that in web.config, both the property "cfgservers" and "configservers" can be used to specify the list of config servers – if cfgservers is present it will be used, otherwise configservers.
In web.config, you can change the default name of the cookies that Ceptor expects the session ID to be located within. By default, it looks at the following cookies: "sclSessionID,ppSessionID,sslsessionid,sessionid" – you can change this in appsettings by adding:
<add key="portalprotect.cookienames" value="ppsession;othercookie" />
Which will cause PP to look for the cookies "ppsession" or "othercookie" and use one of these if present. Normally you would only need this if you change the dispatcher configuration to use a session forwarding cookie with a name other than the default.
Sample configuration
app.config
The following app.config example sets up logging and configures the agent.
<?xml version="1.0"?> <configuration> <!-- Register a section handler for the log4net section --> <configSections> <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net"/> <sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral"> <section name="PortalProtect.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral" allowExeDefinition="MachineToLocalUser" requirePermission="false"/> </sectionGroup> </configSections> <appSettings> <add key="servername" value="webserver1" /> <add key="cfgservers" value="nio://localhost:21233" /> <add key="nowait" value="true" /> <!-- To enable internal log4net logging specify the following appSettings key --> <!-- <add key="log4net.Internal.Debug" value="true"/> --> </appSettings> <!-- This section contains the log4net configuration settings --> <log4net> <appender name="EventLogAppender" type="log4net.Appender.EventLogAppender"> <applicationName value="APP - PortalProtect"/> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%date [%thread] %-5level %logger [%ndc] - %message%newline"/> </layout> </appender> <appender name="PortalProtectAppender" type="PortalProtect.Log.RemoteLogAppender"> <servers value="tcp://localhost:21236"/>" </appender> <!-- Setup the root category, add the appenders and set the default level --> <root> <level value="INFO"/> <appender-ref ref="EventLogAppender"/> <appender-ref ref="PortalProtectAppender"/> <!-- <appender-ref ref="NetSendAppender" /> --> </root> <!-- Specify the level for some specific categories --> <logger name="PortalProtect.Communication.SocketPeer"> <level value="ERROR"/> </logger> </log4net> <startup><supportedRuntime version="v2.0.50727"/></startup> </configuration>
web.config
This is a sample web.config configuration that sets up both a MemberShipProvider, RoleProvider and a ProfileProvider, along with the HttpModule.
<?xml version="1.0" encoding="UTF-8"?> <configuration> <configSections> <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler" /> </configSections> <system.web> <compilation debug="true" /> <authentication mode="Forms"> <forms loginUrl="~/Account/Login.aspx" timeout="2880" /> </authentication> <membership defaultProvider="PPMembershipProvider"> <providers> <clear /> <add name="PPMembershipProvider" type="PortalProtect.Providers.PortalProtectMembershipProvider" applicationName="/" /> </providers> </membership> <profile enabled="true" defaultProvider="PPProfileProvider"> <providers> <add name="PPProfileProvider" type="PortalProtect.Providers.PortalProtectProfileProvider" /> </providers> <properties> <add name="email1" defaultValue="[null]" /> <add name="firstname" defaultValue="x" /> <add name="lastname" defaultValue="[null]" /> <add name="address1" defaultValue="[null]" /> <add name="city" defaultValue="[null]" /> <add name="zipcode" defaultValue="[null]" /> <add name="country" defaultValue="[null]" /> <add name="phone" defaultValue="[null]" /> <add name="phonemobile" defaultValue="[null]" /> </properties> </profile> <roleManager enabled="true" defaultProvider="PPRoleProvider"> <providers> <clear /> <add name="PPRoleProvider" type="PortalProtect.Providers.PortalProtectRoleProvider" applicationName="/" /> </providers> </roleManager> <customErrors mode="Off" /> </system.web> <appSettings> <add key="servername" value="webserver1" /> <add key="cfgservers" value="nio://localhost:21233" /> <add key="nowait" value="true" /> </appSettings> <system.webServer> <modules> <add name="PPAuth" type="PortalProtect.Providers.PPBasicAuthenticationModule, PortalProtect.Agent, Version=1.0.0.0, Culture=neutral, PublicKeyToken=218ecb9b9450d01f" /> </modules> </system.webServer> <log4net> <appender name="EventLogAppender" type="log4net.Appender.EventLogAppender"> <applicationName value="APP - PortalProtect"/> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%date [%thread] %-5level %logger [%ndc] - %message%newline"/> </layout> </appender> <appender name="PortalProtectAppender" type="PortalProtect.Log.RemoteLogAppender"> <servers value="nio://localhost:21236"/>" </appender> <!-- Setup the root category, add the appenders and set the default level --> <root> <level value="INFO" /> <appender-ref ref="EventLogAppender" /> <appender-ref ref="PortalProtectAppender" /> </root> </log4net> </configuration>
Configurations for Testing
If needed for offline testing, Ceptor .NET Agent supports use of an alternate dummy version of the agent, which implements the IAgent interface, but does not provide real data.
<appSettings> <add key="agentimplclass" value="PortalProtect.Agent.Dummy.DummyAgentImpl, PortalProtect.Agent.Dummy, Version=1.0, Culture=neutral, PublicKeyToken=218ecb9b9450d01f" /> </appSettings>
This agent allows creation of a new session, and storing / retrieving state date from it, but the session is only available internally in the .NET agent and definitely not meant for production use. But, if you need to test without having a real Ceptor instance available, this can help you.
Note that this also requires PortalProtect.Agent.Dummy.dll to be available to the application.
If you have special requirements for the behaviour of the agent, you can create your own implementation of IAgent and reference the generated type.
HttpModule
Ceptor .NET Agent HttpModule (PortalProtect.Providers.PPBasicAuthenticationModule) looks at all HTTP requests, and requires basic authentication. This means that it always assumes that a Ceptor dispatcher is present in front of the IIS, since the dispatcher by default forwards the session ID in the HTTP authorization header, which is used for basic authentication.
The Ceptor .NET Agent HttpModule is enabled by adding this to your web.config :
<system.webServer> <modules> <add name="PPAuth" type="PortalProtect.Providers.PPBasicAuthenticationModule, PortalProtect.Agent, Version=1.0.0.0, Culture=neutral, PublicKeyToken=218ecb9b9450d01f" /> </modules> </system.webServer>
What this does, is to make sure that HttpContext.Current.User contains a Ceptor Principal which contains the user identification.
Note that if this HttpModule is not installed, the HttpContext.Current.User will not be correct, and some .NET applications that e.g. require role checking or authorization checks to be performed will not work.
When this module is installed, you can no longer access your website directly, but must access it via the Ceptor Dispatcher.
Note that you can always use PortalProtect.Web.HttpPP.CurrentSession() to retrieve the current session no matter if this HttpModule is installed or not – if the module is not installed, it will instead look for the Ceptor session ID in the cookie ppSessionID or sclSessionID which is one of the two default cookie names Ceptor Dispatcher uses to send the session ID to the IIS.
Membership Provider
A .NET Membership Provider can be installed by adding the following to web.config:
<membership defaultProvider="PPMembershipProvider"> <providers> <add name="PPMembershipProvider" type="PortalProtect.Providers.PortalProtectMembershipProvider" applicationName="/" /> </providers> </membership>
Note that only some of the functionality within a membership provider is supported. Operations that manipulate users, such as creating, or deleting them is not supported. Also querying users via the membership provider is currently not supported.
ValidateUser() with userid/password authentication is supported, and ChangePassword() is supported depending on the authentication plugin which the current user is authenticated with, since it might not support changing of passwords.
Authentication Mode
The preferred authentication in web.config is None, set as follows:
<authentication mode="None"/>
This tells the IIS to use custom authentication, usually provided by installing the Ceptor .NET HttpModule.
Use of Forms Login and Logging off
On occasion, Forms Login is used together with Membership Provider – there is no requirement to use it with Ceptor Membership Provider, but if you do you need to add something like this to your web.xml:
<authentication mode="Forms"> <forms loginUrl="~/Account/Login.aspx" timeout="2880" /> </authentication>
You should note that forms authentication uses its own cookie to track authentication, which conflicts with Ceptor, so you need to keep it in sync with Ceptors session.
If you e.g. use an ASP.NET LoginView like the following, you need to add OnLoggingOut which looks like the following:
<script runat="server"> void HeadLoginStatus_LoggingOut(Object sender, System.Web.UI.WebControls.LoginCancelEventArgs e) { try { PortalProtect.Web.HttpPP.CurrentSession().Logoff(); } catch (Exception) { } } </script> <asp:LoginView ID="HeadLoginView" runat="server" EnableViewState="false"> <AnonymousTemplate> [ <a href="~/Account/Login.aspx" ID="HeadLoginStatus" runat="server">Log In</a> ] </AnonymousTemplate> <LoggedInTemplate> Welcome <span class="bold"><asp:LoginName ID="HeadLoginName" runat="server" /></span>! [ <asp:LoginStatus ID="HeadLoginStatus" runat="server" LogoutAction="Redirect" LogoutText="Log Out" LogoutPageUrl="~/" OnLoggingOut="HeadLoginStatus_LoggingOut"/> ] </LoggedInTemplate> </asp:LoginView>
The OnLoggingOut must call the method which logs off Ceptor session too.
Role Provider
The Role Provider allows access to the list of a users roles, or all available roles in the system.
The Role Provider is enabled by adding the following to web.config:
<roleManager enabled="true" defaultProvider="PPRoleProvider"> <providers> <add name="PPRoleProvider" type="PortalProtect.Providers.PortalProtectRoleProvider" applicationName="/" /> </providers> </roleManager>
Note that Ceptor does not support the functionality that modifies roles using the Role Provider, such as creating or deleting new roles or changing role assignment.
Supported methods are: GetAllRoles(), RoleExists(), IsUserInRole() and GetRolesForUser().
For IsUserInRole() and GetRolesForUser() the username must be a session ID or the name of the user in the current session.
Profile Provider
The profile provider allows access to state variables with the Ceptor session. A state variable is a string stored in the session that can contain anything, from the users address, email, phone number to information about what credentials the user has for other systems, userids for backend systems or other business related information such as the maximum amount the user is allowed to withdraw from an account per day or create loans for without further approval.
The profile provider is enabled by adding the following to web.config:
<profile enabled="true" defaultProvider="PPProfileProvider"> <providers> <add name="PPProfileProvider" type="PortalProtect.Providers.PortalProtectProfileProvider" /> </providers> <properties> <add name="email1" defaultValue="[null]" /> <add name="firstname" defaultValue="x" /> <add name="lastname" defaultValue="[null]" /> <add name="address1" defaultValue="[null]" /> <add name="city" defaultValue="[null]" /> <add name="zipcode" defaultValue="[null]" /> <add name="country" defaultValue="[null]" /> <add name="phone" defaultValue="[null]" /> <add name="phonemobile" defaultValue="[null]" /> </properties> </profile>
Note that the property names depend on which values you store in the session either upon login or later during use. The names above are from the Ceptor Demo Application which uses these properties to store information in.
Ceptor Owin
To use Ceptor Owin, you need to enable it in the IAppBuilder, this is simply done by calling the extension method UseCeptorAuthentication
on the IAppBuilder
object, like this:
using Ceptor.Owin; app.UseCeptorAuthentication(new CeptorAuthenticationOptions { MapClaimName = ((name, value) => name) });
In the CeptorAuthenticationOptions
, you can choose to specify a claims name mapper delegate function/lamda - this allows you to map claim names that are added to the ClaimsPrincipal
Ceptor .NET Agent API
Overview
Ceptor .NET Agent API differs slightly from the java version in syntax, but it essentially offers the same functionality.
As an example, where the java version mainly exposes a number of static methods that all takes a session ID as parameter, e.g. Agent.GetInstance().isLoggedOn(sessionid), the .NET version is more natural to the .NET world, and looks like this:
Agent.GetInstance()[sessionid].IsLoggedOn
Agent.GetInstance()[sessionid] returns an implementation of the ISession interface which exposes methods and attributes that work on this particular session.
Getting the Agent Instance
To obtain an instance of an agent, simply call PortalProtect.Agent.getInstance() – this will return a configured instance of the agent which takes its configuration from web.config if running inside the IIS, and from app.config if running outside the IIS.
You can also manually create a new instance and set it as the default, by calling PortalProtect.Agent.NewInstance(servername, configservers) with the local server/service name and the list of configuration servers. To set it as default, then call PortalProtect.Agent.SetInstance(agent) with the newly created agent.
Getting a Session
To get a session, call Agent.GetInstance()[sessionid] with the appropriate session ID – this will return an instance of PortalProtect.ISession which can be used to reference data within a session, or manipulate the contents of it.
If the session ID was not found – e.g. if the session timed out, it will throw a PortalProtect.SessionNotFoundException.
Getting the Current Session Within an IIS
When running an application inside Internet Information Server, you can obtain a reference to the current session for the user by calling PortalProtect.Web.HttpPP.CurrentSession() – it will return the PortalProtect.ISession object for the current user. It does so by looking at the request cookies and obtaining the session ID from there. The requirement for it to work, is that the request first went through the Ceptor Dispatcher to the IIS server, and that the session cookie forward name in the dispatcher is set to either sclSessionID or ppSessionID. If that is not the case, it will look at the current user for the executing thread, and check if there is a session id present there. If so, it returns that session.
Manipulating State Variables
Once you have a PortalProtect.ISession object, you can use the attribute ISession.StateVariables – it contains a PortalProtect.IState object which contains all current state variables. You can query, delete or add new state variables – refer to the API reference documentation for details.
Reference
Refer to PortalProtect_DotNet_Agent_Documentation.chm in the documentation directory of the Ceptor distribution for complete reference information. Start by looking at PortalProtect.Agent and PortalProtect.ISession, they will provide you with most of the information you need.
Exceptions and Errors
The documentation mentioned above documents which methods throw which methods.
Below is an overview of common exceptions and their likely reasons.
PortalProtectException("Unable to connect to " + urls);
If nowait=true in the configuration, the agent will throw this exception if unable to contact the session controller. If nowait=false (which is the default), the thread calling the Ceptor Agent will block until a connection is available.
PortalProtectException("No connection to Session Controller, cluster: " + clusterID);
If the agent has multiple clusters of PortalProtect Servers configured, and the connection to a session controller fails, this exception will be thrown assuming nowait=true – if nowait=false, the agent will block until a connection is made.
SessionNotFoundException("No session found with ID: " + sessionID);
PortalProtect was unable to find a session with the specified ID – the session might have timed out, or it might belong to another session controller in another cluster than the one the agent is connected to.
SessionNotFoundException("Session ID " + sessionID + " is no longer valid.");
The application has a reference to an ISession object it previously retrieved and is attempting to use that session, but that session has timed out and is no longer valid.
AclNotFoundException("ACL " + aclName + " not found");
The application has called CheckPermission() on a session, but the ACL name given as input does not exist in the Authorization plugin configured on the PortalProtect Server.
SessionNotFoundException("Illegal session ID: " + requestID);
A client has attempted to call the agent with a non-valid session ID – e.g. with a username or similar instead of a session ID.
ConfigurationNotFoundException("configuration for " + serverName + " not found");
The name of the agent is not recognized by the PortalProtect Server – the server must have a configuration matching the configured name of the agent. See the configuration for the property "servername" for info.
PortalProtectException("Unable to initialize PortalProtect - requires servername specified in appSettings");
The configuration property "servername" is missing from the applications settings in app.config or web.config.
PortalProtectException("Unable to initialize PortalProtect - requires configservers specified in appSettings");
The configuration property "configservers" is missing from the applications settings in app.config or web.config.
PortalProtectException("Timeout (after " + timeout + " msecs.) waiting for reply from server for command: " + cmd.GetType().FullName);
The PortalProtect Server has not responded to a request within the time limit, check the log on the server for more information.
PortalProtectException("Unable to find applet signer certificate for provider: " + nemidProviderID);
The application has attempted to use a provider ID for generating NemID logon applet that is not configured on the PortalProtect Server. See the server configuration for a list of which Ids are configured and available for use by applications.
SessionNotFoundException("No current HTTPContext"); SessionNotFoundException("Keys: " + keys); SessionNotFoundException("No current user in HTTPContext"); SessionNotFoundException("Current user is not PPPrincipal, but " + HttpContext.Current.User.GetType()); SessionNotFoundException("No session in PPPrincipal within current HttpContext");
Various exceptions thrown by HttpPP.CurrentSession() – they either indicate that the PortalProcect HTTP Module was not installed in the IIS, or that the request did not go via a PortalProtect Dispatcher before ending up in the IIS and instead went directly to the IIS server.
Other typical errors:
In addition to these exceptions, the installed authentication plugins on the Ceptor server are capable of causing PortalProtectExceptions to be thrown by the agent – e.g. when calling Login() or Confirm().
Authentication plugins are encouraged to use error codes defined in the java interface dk.itp.security.passticket.server.AuthErrorCodes, but there is no guarantee that they do so.
Below is a list of the common error codes defined:
/** No user could be found */ public static final short ERROR_USERNOTFOUND = 1; /** Invalid password */ public static final short ERROR_INVALIDCREDENTIALS = 2; /** General error trying to login */ public static final short ERROR_GENERALERROR = 3; /** User is locked */ public static final short ERROR_LOCKED = 4; /** User is marked as inactive */ public static final short ERROR_INACTIVE = 5; /** Userid already exists (when creating new user) */ public static final short ERROR_USEREXISTS = 6; /** Certificate could not be read/parsed */ public static final short ERROR_CERTIFICATE_UNREADABLE = 7; /** Certificate is not yet valid */ public static final short ERROR_CERTIFICATE_NOTYETVALID = 8; /** A certificate in the chain which is not allowed to sign another certificate has been used to do it anyway, so the chain is not trusted */ public static final short ERROR_CERTIFICATE_CHAINBROKEN = 11; /** The certificate did not match the one in our database */ public static final short ERROR_WRONG_CERTIFICATE = 12; /** The signed text did not match expected text */ public static final short ERROR_SIGNTEXTINVALID = 13; /** User is being created - not active yet */ public static final short ERROR_UNDER_CREATION = 14; /** Need additional one-time password */ public static final short ERROR_NEED_OTP = 15; /** No mobile number present, cannot send SMS */ public static final short ERROR_NOMOBILEPHONE = 16; /** NemID SSO not possible, user is not authenticated with 2factor auth */ public static final short ERROR_NONEMIDSSO_MISSING2FACTOR = 17; /** CA (certificates issuer) was not found and is unknown to current configuration */ public static final short ERROR_UNKNOWN_CA = 1001; /** PID service not implemented by CA, could not validate connection between CPR and PID */ public static final short ERROR_PID_SERVICE_NOT_AVAILABLE = 1002; /** general PID error, check log */ public static final short ERROR_PID_ERROR = 1003; /** certificate was found on revocation list */ public static final short ERROR_CERTIFICATE_REVOKED = 1004; /** certificate expired or not yet valid */ public static final short ERROR_CERTIFICATE_EXPIRED= 1005; /** certificate error, ex. key errors or signature error, check log */ public static final short ERROR_CERTIFICATE_ERROR= 1006; /** certificate error, ex. key errors or signature error, check log */ public static final short ERROR_CRL_NOT_FOUND= 1007; /** general PID error, check log */ public static final short ERROR_PID_CPR_NOT_MATCH = 1008; /** Attribute lookup service not implemented by CA */ public static final short ERROR_ATTR_SERVICE_NOT_AVAILABLE = 1009; /** Unable to lookup OCES certificate */ public static final short ERROR_OCESCERTLOOKUP_FAILED = 1010; /** NemID Applet Parameter signing failed */ public static final short ERROR_APPLETPARAMSIGN_FAILED = 1011;
The relevant error code is available in the "ErrorCode" property on the PortalProtectException, and if the authentication plugin provides an "ErrorMessage" meant for displaying to the user, this is available too in the "ErrorMessage" property.
NemID Integration
Ceptor .NET Agent provides easy access to NemID. You can get a signed applet tag with the relevant parameter by using the class PortalProtect.NemID.NemIDAppletElementGenerator.
The following is part of an example (error handling simplified) of using NemID to login (see full example in the samples/integration/dotnet directory in the Ceptor Distribution).
This snippet originates from NemIDLogin.aspx which provides the user with the NemID login applet, and processes the XMLDSIG document sent from the applet.
<%@ Page Title="NemID Login" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true" CodeFile="NemIDLogin.aspx.cs" Inherits="About"%> <script runat="server"> String tag = ""; private void Page_Load(object sender, System.EventArgs e) { if (this.IsPostBack){ PortalProtect.ISession session = PortalProtect.Web.HttpPP.CurrentSession(); if ("logonok".Equals(this.action.Value)) { session.Login("nemid", this.message.Value, PortalProtect.AuthTypes.AUTHTYPE_X509_CA, null); // Verify that the challenge is what we put in there to prevent replay attacks if (!PortalProtect.NemID.NemIDHelper.VerifyChallenge(session.StateVariables["challenge"])) { session.Logoff(); // Replay attack ? throw new PortalProtect.PortalProtectException("Challenge from applet did not match expected value"); } else { // Challenge was ok. Response.Redirect("/", true); return; } } else { throw new ApplicationException(PortalProtect.NemID.NemIDHelper.GetErrorText(this.message.Value)); } } } else { int providerid = 10; PortalProtect.NemID.NemIDAppletElementGenerator applet = new PortalProtect.NemID.NemIDAppletElementGenerator(PortalProtect.Web.HttpPP.CurrentSession(), PortalProtect.AuthTypes.AUTHTYPE_X509_CA, providerid); applet.setChallenge(PortalProtect.NemID.NemIDHelper.GenerateChallenge()); applet.setUrlPrefix(PortalProtect.NemID.NemIDAppletElementGenerator.URLPREFIX_PREPROD); tag = applet.generateLogonAppletElement(); } </script> <asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent"> </asp:Content> <asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent"> <h2>NemID Login</h2> <input id="message" runat="server" value="" type="hidden"/> <input id="action" runat="server" value="" type="hidden"/> <script type="text/javascript" language="JavaScript"> function onLogonOk(signature) { document.forms[0]['Mstr$MainContent$message'].value = signature; document.forms[0]['Mstr$MainContent$action'].value = "logonok"; document.forms[0].submit(); } function onLogonCancel() { document.location = "/"; } function onLogonError(reason) { document.forms[0]['Mstr$MainContent$message'].value = reason; document.forms[0]['Mstr$MainContent$action'].value = "logonerror"; document.forms[0].submit(); } </script> <%=tag %> </asp:Content>
In this example, we start by checking if we are handling a POST from the applet with the signed output from the applet (or possible an error code) – and calls PortalProtect to login using the data exactly as received from the applet.
If it is not a POST, we are creating the applet tag using NemIDApletElementGenerator to do all the dirty work. It in turn calls Ceptor Server which knows all about the keys and certificates required to do the signing. See Ceptor Users Guide for details on the configuration of the server.
We basically just provide a NemID Provider ID which identifies the certificate to use, and private key to use for signing, and then wi set the URL to point to the preproduction or production NemID environment.
When that is done, we add the javascript required by the NemID Applet, and the generated applet tag and we are done.
Note that this example also generates a challenge value that is added as parameter to the applet – this challenge is then verified (done using PortalProtect.NemID.NemIDHelper) when we receive the data from the applet to ensure that the applet is responding to this particular request. If we do not do this, we risk someone recording an earlier sent login xmldsig from the applet and just replaying it to us.
When signing documents, instead of calling applet.generateLogonAppletElement() to generate the applet tag, just call applet.setSigntext() with the text to sign, and applet.generateSignAppletElement() to generate the applet tag instead. When receiving the data from the applet, call session.Confirm() instead of session.Login() – see the NemIDSign.aspx file in the sample for details.
The parameters to the applet are as described in DanIDs documentation.
Using Ceptor Owin
Ceptor Owin enables you to run a web server in a standalone process or within the IIS server.
Below is a small example of a standalone application which setups Ceptor Authentication - this will then expect that the application is no longer called directly, but always via a Ceptor Gateway - it looks for the session ID in the configured cookies, and picks out the user information and provides all state variables as claims within a ClaimsPrincipal.
The MapClaimName
delegate in the CeptorAuthenticationOptions
makes it possible for you to map claim names if you want specific state variables in the session to be mapped to specific claims. If you return null, the specific claim will be ignored and not added to the Identity.
Below is an example
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Net.Http; using System.Threading.Tasks; using Microsoft.Owin.Hosting; using Owin; using Ceptor.Owin; using PortalProtect; using System.Security.Claims; namespace Ceptor.Owin.Test { public class Class1 { static void Main(string[] args) { Console.WriteLine("Attempting to start small local webserver on port 12345"); // Not needed, but creates a session just to verify that the connection to Ceptor server works ISession session = Agent.GetInstance().CreateSession("127.0.0.1"); Console.WriteLine("Created new session: " + session.SessionID); using (WebApp.Start<Startup>("http://localhost:12345")) { Console.ReadLine(); } } } public class Startup { public void Configuration(IAppBuilder app) { app.UseCeptorAuthentication(new CeptorAuthenticationOptions { MapClaimName = ((name, value) => name) }); app.Map("/test", config => { config.Run(context => { context.Response.ContentType = "text/html"; if (context.Request.User != null) { ClaimsPrincipal cp = context.Request.User as ClaimsPrincipal; Console.WriteLine("isAuthenticated: " + cp.Identity.IsAuthenticated); Console.WriteLine("Claims: " + cp.Claims); foreach (var claim in cp.Claims) { Console.WriteLine(claim.Type + "=" + claim.Value); } } return context.Response.WriteAsync("<html><body>test</body></html>"); }); }); app.UseWelcomePage("/"); } } }
Using Authorization
Configuring Authorization Checks
Configuration-based authorization checks rely on standard .NET mechanisms which uses current principal, so for them to work, the Ceptor .NET Agent HttpModule must be enabled for the application.
You can use e.g. <authorization> tags in the web.config file (see Microsofts documentation for details), or you can authorize individual methods like described here: http://msdn.microsoft.com/en-us/library/system.web.mvc.authorizeattribute.aspx
Example
[Authorize(Roles = "Admin, Super User")] public ActionResult AdministratorsOnly() { return View(); }
Programmatic Authorization
Once you obtained a PortalProtect.ISession object, you can call CheckPermission() to check if the user as the given ACL permission, or you can call IsMemberOfGroup() to check group membership. If you want to find out if access to an URL is allowed, call IsUrlAllowed() to check if the user in the session has access to that particular URL. See the reference documentation for details. You can also call IsInRole() on the current windows user principal, assuming the Ceptor .NET Agent HttpModule is installed as described earlier in this document.
Examples
This section contains samples of various usage of the APIs
Getting the configuration from the config server
NameValueCollection config = Agent.GetInstance().getConfiguration();
Creating a new session programmatically
ISession session = agent.CreateSession("127.0.0.1");
Logging a user in using userid and password
session.Login("admin", "password");
Checking a permission
session.CheckPermission("user.create")
Creating NemID Login Applet tag
NemIDAppletElementGenerator applet = new NemIDAppletElementGenerator(session, 15, 10); applet.setChallenge(NemIDHelper.GenerateChallenge()); applet.setUrlPrefix("https://appletk.danid.dk"); String tag = applet.generateLogonAppletElement();
Creating a NemID Sign applet tag
NemIDAppletElementGenerator applet = new NemIDAppletElementGenerator(session, 15, 10); applet.setChallenge(NemIDHelper.GenerateChallenge()); applet.setUrlPrefix("https://appletk.danid.dk"); applet.setSignText("I wow to be a better person. Next year.", "plain"); applet.addSignedParameter("always_embedded", "true"); tag = applet.generateSignAppletElement();
Sharing state between applications
Setting the state:
session.StateVariables["mystate"] = "Some state I want to share between applications";
And querying it from other applications:
string mystate = session.StateVariables["mystate"];
… or you can configure the Ceptor Dispatcher/Gateway to send that state in the HTTP headers.
Listing all configured groups
Console.WriteLine("Groups:"); foreach (GroupEntry entry in Agent.GetInstance().GetAvailableGroups(null)) Console.WriteLine(entry.ToString());
© Ceptor ApS. All Rights Reserved.