Skip to content

Custom Authentication Process

Michael Hallock edited this page Aug 31, 2016 · 2 revisions

The actions to execute after a successful federated SignOn or Logout can be customized as necessary to accommodate special use cases. Understanding the [Default Authentication Process](Default Authentication Process) is a good idea before extending this process.

The most common use case is to replace the FormsAuthenticationAction with a custom implementation. For instance, if the identity provider does not provide a NameIDFormat which can be easily mapped to a local user, but does provide attributes that are, then the FormsAuthenticationAction can be replaced with a custom MyAuthenticationAction.

There are two steps to do this.

Create a New Action

Creating a new action is as simple as inheriting from SAML2.Actions.IAction and implementing your custom authentication scheme. Below is an example of such an implementation, which identifies local users by a RequestedAttribute that the identity provider has passed back instead of the default <Subject>.

public class MyAuthenticationAction : IAction
{
    #region Implementation of IAction

    private string _name = "MyAuthentication";

    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }

    /// <summary>
    /// Action performed during login.
    /// </summary>
    /// <param name="handler">The handler initiating the call.</param>
    /// <param name="context">The current http context.</param>
    /// <param name="assertion">The saml assertion of the currently logged in user.</param>
    public void SignOnAction(AbstractEndpointHandler handler, HttpContext context, Saml20Assertion assertion)
    {
        // Ensure that the necessary attributes have been returned
        if (!Saml20Identity.Current.HasAttribute("identifier"))
        {
            throw new ArgumentException("SAML Response did not contain the identifier attribute.");
        }

        // Ensure that there is a value passed back for identifier
        if (!Saml20Identity.Current["identifier"].Any(x => x.AttributeValue.Length > 0))
        {
            throw new FormatException("SAML Response contained the identifierattribute, but did not include a value.");
        }

        var identifier = Saml20Identity.Current["identifier"].FirstOrDefault(x => x.AttributeValue.Length > 0).AttributeValue.FirstOrDefault();
        var user = LookupUserByIdentifier(identifier);

        // Check for existing user
        if (user == null)
        {
            context.Response.Redirect(url.Action("AccessDenied", "Error"));
        }

        // Sign in
        FormsAuthentication.SetAuthCookie(user.Name, false);  
    }

    /// <summary>
    /// Action performed during logout.
    /// </summary>
    /// <param name="handler">The handler.</param>
    /// <param name="context">The context.</param>
    /// <param name="IdPInitiated">During IdP initiated logout some actions such as redirecting should not be performed</param>
    public void LogoutAction(AbstractEndpointHandler handler, HttpContext context, bool IdPInitiated)
    {
        FormsAuthentication.SignOut();
    }

    #endregion

    ...
}

Replacing Action in Configuration

To enable the custom authentication action instead of the FormsAuthenticationAction, the [<actions>](Actions Element) element must be added / modified to override the defaults.

<saml2>
    ...
    <actions>
    <clear/>
    <action name="SetSamlPrincipal" type="SAML2.Actions.SamlPrincipalAction, SAML2" />
    <action name="MyAuthentication" type="MyProject.MyAuthenticationAction, MyProject" />
    <action name="Redirect" type="SAML2.Actions.RedirectAction, SAML2" />
    </actions>
    ...
</saml2>

Summary

In summary, it easy to override the default behavior to accommodate custom authentication workflows. In this case, the <Subject> is completely ignored, and the attributes passed back are used to find a local identity. This action could just as easily be extended to automatically create a user if they don't exist, etc.

Clone this wiki locally