Everything .NET Devs Need to Know About Authenticating With SAML2
Written by Zack Schwartz
Intro
In this post, we will dive into the world of SAML2. With over 12 years of experience building SSOs of various types, I have gained a wealth of knowledge on the subject. From popular standards like OAuth, JWT, and SAML to custom-built and sometimes quirky SSOs, I have likely built hundreds of them.
This post will help you understand the core elements of SAML that you need to know in order to integrate it into your application. The post will not be completely exhaustive, but it will cover most of the common items for implementing SAML validation. It is important to note that this post focuses on consuming SAML, not acting as a SAML provider.
SAML Terminology and Definitions
Here are key terminology that developers and IT folks use.
IdP = Identity provider. This is the platform that users will authenticate through. Often times the identity provider is an authentication provider such as Google Workspace, Okta, Auth0, or Azure Active Directory. Other times it is a membership platform like Fonteva or NimbleAMS (Salesforce based), or something custom.
SP = Service provider. This is the system that the user is hoping to SSO into, and will consume and validate the SAML payload.
Assertions. These are the XML attributes as part of the SAML payload that provide the user profile information such as the user ID, name, email, etc.
x509 Certificate. This is used to validate that the SAML payload is legitimate.
The Simplest Implementation: IdP-initiated, validation only
Let's start with the base case SAML login. IdP-initiated means that the identity provider can log the user in with one-click, or a direct login. An example of this in the wild would be if your organization uses Google Workspace and has external products they want you to be able to single sign on into with your Google credentials. The IT administrator would add the appropriate SAML application with the necessary settings and then it might appear for you like so:
By clicking on the application, it will SSO you directly into the application.
As a .NET developer, all you might need to do on your side is redirect the user to the designated sign-in url on the identity provider side if they are not already logged in to your application.
We will continue with the Google Workspace example because it is fairly representative of most basic SAML implementations.
On the Google Workspace side, the IT administrator will have to create a new "Web or mobile app" with which they will have to enter at a minimum:
ACS URL. This is the URL to your login endpoint. When there is a login attempt coming from your IdP, they will send a POST request to this ACS url with a SAMLResponse as the form variable. In the case of Raytha, your ACS url will be https://[your_domain]/raytha/login/saml/[developer_name]
Entity Id. This is usually just a chosen name by you, typically one word only.
In response, the IT administrator of Google Workspace should provide you with the SSO Url (aka the Login Url) as well as the x509 certificate.
Sometimes, this exchange of information is done via metadata xml. And furthermore, some IT administrators may expect you to provide this via an endpoint that they can consume.
If you need to provide metadata.xml or build an endpoint, you can go here: https://www.samltool.com/sp_metadata.php and fill out the basic info and follow that format.
The IT administrator and you also need to agree on what SAML Assertions will be passed over through the SAMLResponse payload. Most often, the ID assertion is the email address or User ID of the user.
In addition to the ID attribute, the IT administrator can also include additional SAML assertions about the user. This is typically done through the Attribute Mapping user interface in Google Workspace, but every IdP will have their own methodology.
In Raytha, you can automatically set a user's user group upon logging in with SSO if you properly set the groups attribute as shown above.
Let's recap where we're at:
You gave the IT administrator on the IdP side an endpoint where you receive a POST request for the SAMLResponse called an ACS Url. You also provided an EntityId name.
You received an SSO / Login Url and x509 certificate from the IT administrator.
The IT administrator mapped profile Attributes for the SAML Assertions.
In the Raytha platform, for example, this is all you need to hookup a SAML out of the box by plugging in those details:
At this point, you should be ready to write some .NET code.
Remember the ACS Url that you promised would receive a POST request to handle the SAMLResponse payload? Below is a sample from Raytha's code base.
[Route(RAYTHA_ROUTE_PREFIX + "/login/saml/{developerName}", Name = "adminloginsaml")]
[HttpPost]
public async Task<IActionResult> Saml(string developerName)
{
var samlResponse = Request.Form["SAMLResponse"].ToString().Replace(" ", "+"); //can't figure out why spaces come in on the post
string returnUrl = string.Empty;
if (Request.Form.ContainsKey("RelayState") && Request.Form["RelayState"].Count == 1)
returnUrl = Request.Form["RelayState"].ToArray()[0].ToString();
var command = new LoginWithSaml.Command
{
DeveloperName = developerName,
SAMLResponse = samlResponse
};
var response = await Mediator.Send(command);
return await HandleSingleSignOnResult(response, returnUrl);
}
You may also receive a RelayState value which can be used to redirect the user to their originally intended destination.
Once you receive the SAMLResponse, you have to validate the SAMLResponse. This is where the x509 certificate comes in.
Raytha has a class that takes in the xmlPayload and x509 cert (stored either in db, or a file on disk), and whether the payload is base64 encoded or not and then offers a method for testing if the SAMLResponse is valid.
It also contains helper methods for parsing out SAML Assertions for email, name, and user groups, some of which may be specific to Raytha, but that you can modify for your own purposes.
Once you have determined if the SAMLResponse is valid, and you are able to parse out the SAML Assertions, you can log that user in to your application as you would any user.
Slightly More Complicated: SP-Initiated with AuthnRequests
The SP-initiated method is slightly more complicated than IdP-initiated. The most important thing to understand about SP-initiated is that you still need to do everything from the IdP-initiated workflow above, but now you have an extra step in the beginning.
Basically, what this means is that the IdP will not honor any login attempts unless a request to do so comes from the SP (your application). This is accomplished via a SAMLRequest when you are redirecting to the IdP.
A SAMLRequest is often base64 encoded and appended to the SSO url with ?SAMLRequest={your_base64_payload}.
Most Complex: Signing, Encrypting, and Decrypting Payloads
I have found that the above two scenarios cover 90% of SAML sso integrations. However, there are twists and variations of them that involve signing and encryption. These additional complexities can be quite painful for a developer to implement.
1) Encrypted SAMLResponse. One of the more common complexities that can arise in SAML SSO integrations is when an IdP decides to encrypt the SAMLResponse payload. The SAMLResponse payload typically contains sensitive information, and encrypting it can help ensure the security of the data in transit. If an IT administrator requires the payloads to be encrypted, you'll need to set up a public-private key pair on your application or vault.
You will publish your own x509 certificate containing the public key that the IdP will use to encrypt the payload before sending the SAMLResponse. Your application or vault would then use its private key to decrypt the payload.
2) Signed AuthnRequest. In an SP-initiated workflow, the IdP may require that AuthnRequests be signed by the service provider. This is another scenario that can add complexity to SAML SSO integrations. To sign the AuthnRequest, the service provider would use its private key to sign the request, and the IdP would use the service provider's public key to validate the request. This helps ensure the authenticity and integrity of the AuthnRequest.
3) Combo. The least common but still possible scenario is when an IdP requires that the AuthnRequest be both signed by the service provider's private key and encrypted with the IdP's public key. This scenario would require additional configuration and setup to accommodate the IdP's requirements.
ENTREPRENEUR & SOFTWARE ENGINEER, AUSTIN, TX I enjoy tackling a wide array of business challenges ranging from front line product support and operations to sales and marketing efforts. My core expertise is in software development building enterprise level web applications.