Authentication

With C#

723 views June 12, 2017 June 8, 2018 0

This guide will show you how to verify a SAML response using C# and the client kit for .NET. The example code in this section demonstrates verification of a SAML response in an ASP.NET MVC web application. The exact same concept will also work for ASP.NET Web Forms.

You may download the code in this guide from https://github.com/signicat/auth.

Include the .NET client kit in your project

The first step is to download the client kit for C#. Unzip the file, copy the Signicat.Basic.dll to your project and include it as a reference.

Create the method which will receive the SAML response

The Verify Method

public ActionResult Verify(string SAMLResponse)
{
    try
    {
        string recipient = Url.Action("Verify", "Auth", null, Request.Url.Scheme);
        IEnumerable<Signicat.Basic.Attribute> authAttributes;
        authAttributes = Signicat.Basic.Saml.Verify(SAMLResponse, Infrastructure.Test, 10, recipient);
        // The attributes will vary between different id methods.
        // This is an example for Norwegian BankID.
        // Choose your attributes according to your requirements.
        string nationalId = authAttributes.First(a => a.Namespace == "national-id" && a.Name == "no.fnr").Value;
        string plainName = authAttributes.First(a => a.Name == "plain-name").Value;

        // Demo code for illustration purposes.
        // You are not required nor encouraged to use sensitive information in your cookies.
        FormsAuthentication.SetAuthCookie(nationalId, false);
        return RedirectToAction("Granted", new { name = plainName });
    }
    catch (SignicatException x)
    {
        // Could not verify SAML response
        Log(x);
        return Redirect("Denied");
    }
    catch (ArgumentException x)
    {
        // The input to the Verify method is invalid
        Log(x);
        return Redirect("Denied");
    }
}

Code walkthrough

Code Description
string recipient = Url.Action(“Verify”, “Auth”, null, Request.Url.Scheme); A SAML response contains information about the intended recipient of it, i.e. the URL of the method which should receive it. This is a safety mechanism to make sure that we’re not verifying SAML responses that were intended for another application. If this application was running on http://localhost:8080 then the call to Url.Actionyields http://localhost:8080/auth/verify. (In production you are required to use https.)
authAttributes = Signicat.Basic.Saml.Verify(SAMLResponse, Infrastructure.Test, 0, recipient); The Verify method expects the base 64 encoded SAML response, the infrastructure (Test or Production), the time skew and the previously constructed recipient URL. If successful, the invocation returns a set of all the attributes in the SAML response. If unsuccessful, it will throw an exception and the exception message will tell you why.
string nationalId = authAttributes.First(a => a.Namespace == “national-id” && a.Name == “no.fnr”).Value; This is a regular LINQ query to extract the relevant information from the SAML response. The code you write here will depend on what id method is in use and which information you are interested in. See example SAML responses for different id providers here.
FormsAuthentication.SetAuthCookie(nationalId, false); Demo code. Most likely you want to map a national identity number of a person to some kind of user id in your application. You are not required nor encouraged to use the national identity number in your cookies.
return RedirectToAction(“Granted”, new { name = plainName }); Authentication is complete and you may redirect the user to wherever you’d like.

Error situations

The following exceptions and messages may be produced when verifying the SAML response:

Exception Description
ArgumentException – “SAML Response cannot be null” For example, this may happen if a user is sent to the Verify action without having authenticated first
ArgumentException – “Recipient is undefined” Occurs when you have failed to define the intended recipient of the SAML response
SignicatException  – “The SAML response is not signed” This would theoretically happen if an attacker was trying to send unsigned SAML responses to your application
SignicatException – “The response ID ffff-ffff-ffff-ffff-ffff has already been used and cannot be reused” In the event that an attacker is able to intercept a SAML response and tries to re-use it, then this exception message would be produced in order to indicate that an already consumed SAML response is attempted to be reused
SignicatException – “Assertion conditions not present” Signicat will always include assertion conditions (such as the validity period) in a SAML response. If these are missing, the response is considered invalid.
SignicatException – “HasNotBefore and HasNotOnOrAfter is required” Specifically, the validity period must always be specified.
SignicatException – “The SAML response is not yet valid …” This situation would occur if there is a mismatch between Signicat server clocks and your server clocks. Please refer to Use time skew when verifying SAML responses.
SignicatException – “The SAML response validity period has expired …” This situation would occur if there is a mismatch between Signicat server clocks and your server clocks. Please refer to Use time skew when verifying SAML responses.
SignicatException – “Recipient mismatch …” Occurs if the SAML response contains a recipient which differs from the input to the Verify method.
SignicatException – “Failed to verify SAML response signature …” Indicates that the SAML response has been tampered with, or that the certificate with which it was signed is unexpected or not issued by the expected issuer. The exception message will contain information regarding why the signature verification failed.
SignicatException – “SAML response status code = …, message = …” The SAML response was verified, but the authentication process was not successful. The end user may have cancelled or did not have the right certificate etc.

Full example code

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Web.Mvc;
using System.Web.Security;
namespace Signicat.Basic.Example.Controllers
{
    public class AuthController : Controller
    {
        public ActionResult Index()
        {
            // For more information about redirecting the user to Signicat and the Signicat URL format,
            // see https://developer.signicat.com/documentation/authentication/get-started-with-authentication/
            string target = Url.Action("Verify", "Auth", null, Request.Url.Scheme);
            string targetUrlEncoded = Url.Encode(target);
            string authenticationUrl = "https://preprod.signicat.com/std/method/shared/?id=nbid:demo:nb&target=" + targetUrlEncoded;
            return Redirect(authenticationUrl);
        }

        public ActionResult Verify(string SAMLResponse)
        {
            try
            {
                string recipient = Url.Action("Verify", "Auth", null, Request.Url.Scheme);
                IEnumerable<Signicat.Basic.Attribute> authAttributes;
                authAttributes = Signicat.Basic.Saml.Verify(SAMLResponse, Infrastructure.Test, 10, recipient);
                // The attributes will vary between different id methods.
                // This is an example for Norwegian BankID.
                // Choose your attributes according to your requirements.
                string nationalId = authAttributes.First(a => a.Namespace == "national-id" && a.Name == "no.fnr").Value;
                string plainName = authAttributes.First(a => a.Name == "plain-name").Value;
                FormsAuthentication.SetAuthCookie(nationalId, false);
                return RedirectToAction("Granted", new { name = plainName });
            }
            catch (SignicatException x)
            {
                // Could not verify SAML response
                Log(x);
                return Redirect("Denied");
            }
            catch (ArgumentException x)
            {
                // The input to the Verify method is invalid
                Log(x);
                return Redirect("Denied");
            }
        }

        [Authorize]
        public ActionResult Granted(string name)
        {
            ViewBag.Name = name;
            ViewBag.UniqueId = User.Identity.Name;
            return View();
        }

        public ActionResult Denied()
        {
            return View();
        }

        private void Log(Exception x)
        {
            Trace.WriteLine(x.Message + "\n\n" + x.StackTrace);
        }
    }
}

Was this helpful?