1
votes

I'm replacing our in-house invented authentication service with Okta and need to support SSO with Saml2 into our application. Okta can be configured as Service Provider, send SAMLRequest and receive and validate SAMLResponse. So far so good: I was able to configure Identity Provider in Okta, receive the assertion and, according to logs, the assertion was successfully validated and user auto provisioned.

But what happens from now?

  1. In Idp initiated flow how can I redirect the user to application url? Is RelayState configured by Idp is only the option? I don't like the idea that all our clients will have to make a change if we change our app url. I'd expect the possibility to link the Idp to Okta application, so the redirect url will be configured by this application.
  2. After redirecting to our application site, how do I authenticate the user in Asp.Net Core? I implemented custom AuthenticationHandler that reads sid cookie that was set by Okta on redirect and retrieve the session information from Okta. From there I get user name and create Principal. This approach works but looks me wrong - it's too manual, I'd expect Okta.Sdk to do that for me (if this is correct way for authentication).
  3. After successful assertion validation is it possible to exchange saml token to OAuth token for authentication in application?
2

2 Answers

0
votes

Answers to your questions based on pure SAML approach:

  1. You can use SP-initiated SSO - you'll achieve the same goal of landing on some page. Let's say your application acting as a SAML service provider resides at my.app.com and you want the users to land on https://my.app.com/foo/bar after they're authenticated. Publishing https://my.app.com/foo/bar to your clients/users will result in SP-init flow being kicked off when they eventually click on this link. After some back-and-forth between IdP and SP, the user will land on https://my.app.com/foo/bar as their final destination.

You can implement the same idea with IdP-initiated flow via an additional redirect in your app or upstream of your app in your stack. This is sometimes called a vanity URL. For example, if the vanity URL is https://my.app.com/home, your app or another upstream component can translate this URL to another, target URL and issue a redirect to the target. The target URL can kick off an IdP-initiated flow with RelayState or a SP-initiated flow as described above. Your app or upstream component has to maintain the mapping between vanity and target URLs.

  1. You want Okta to be your SAML identity provider and your application to be the SAML service provider. You tried this cookie-based workaround because you didn't understand how to implement SAML in your .NET app.

Okta does not provide a SAML toolkit or SDK for .NET apps acting as SAML service providers, they recommend 3rd party libraries. ASP.NET doesn't support SAML out of the box...which is another reason Okta recommends 3rd parties. (Microsoft is not one of these 3rd parties). Two popular, free and widely used choices are Sustainsys and ITFoxTec. Have a look at their docs, pick one and implement it. You shouldn't have to resort to your cookie workaround after that.

  1. After the SAML response is processed by your application and the reply (if any) is generated, the authentication step is done and the identity of a principal is known. Your application can now do whatever you want as a next step, including acquiring an oAuth token. This next step is not part of SAML protocol...unless it's a fresh/new SAML flow, that is.

Answers to your questions based on the approach where you use Okta as the SAML service provider and identity store with another SAML identity provider. The flow is 2 hops: SAML IdP -> Okta as a SAML SP -> your app.

Your app has to integrate with Okta via front-channel callback (SAML-like) or API callback (oAuth-like). The latter more or less requires the former although there are lots of variations in this path. The answers below assume a front-end callback style of integration.

  1. Your app will be "invoked" via RelayState by having Okta redirect to the URL in RelayState after it processes the SAML response from the first hop. If you make your RelayState be https://my.app.com/foo/bar, then after some back-and-forth between IdP and SP and your app, the user will land on https://my.app.com/foo/bar as their final destination. Your app will have to trigger this sequence by activating the 1st hop via either IdP-initiated or SP-initiated SAML flow.

  2. In a front-end callback style of integration, you'll be using Microsoft interfaces + Okta libs as shown in Okta's example.

  3. With Okta as your identity store and an authenticated principal, you can get tokens from Okta via additional steps. If your goal is to enable API calls into your app from a 3rd party via Okta, look into API callback style of integration.

0
votes

Finally I was able to make it work the way I wanted. Currently I have problems with Idp-initiated flow, I'll update the answer if I find the solution.

  1. First configure your organization's Okta as Saml Sp for external Saml IdP. Api documentation is here. UI instructions are here. You should receive the information for SAML PROTOCOL SETTINGS (IdP Issuer URI, IdP Single Sign-On URL and IdP Signature Certificate) from your external IdP.

  2. Next step create Web application in Okta. Your Login redirect URIs should finish with /authorization-code/callback otherwise you will have to configure it in CallbackPath property in the code (see below). You don't need to implement this endpoint, the framework does it for you.

  3. Now install Okta.AspNetCore nuget and add this code in ConfigureServices method

     services.AddAuthentication(options =>
     {
         options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
         options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
         options.DefaultChallengeScheme = OktaDefaults.MvcAuthenticationScheme;
     })
     .AddCookie()
     .AddOktaMvc(new OktaMvcOptions
     {
         OktaDomain = "<okta_domain>",
         ClientId = "<app_client_id>",
         ClientSecret = "<app_client_secret>",
         Scope = OktaDefaults.Scope,
         //CallbackPath = "/authorization-code/callback" <= it's default value, change it if required
     });
    

You will find app_client_id and app_client_secret on the bottom of General tab of web application created in step 2.

  1. Now the most interesting part. If you want to trigger authentication process for internal users (not external Saml Idp users) you call ChallengeAsync method. Something like this:

     [ApiController]
     [Route("[controller]")]
     public class AuthController : ControllerBase
     {
         [HttpGet("login")]
         public async Task Login()
         {
             var properties = new AuthenticationProperties
             {
                 RedirectUri = "/"
             };
    
             await HttpContext.ChallengeAsync(properties);
         }
     }
    

Whenever you hit /auth/login url, you will be redirected to Okta for authentication.

When you want to trigger authentication process for an external Idp users you should add idp parameter in AuthenticationProperties class.

[ApiController]
[Route("[controller]")]
public class SsoController : ControllerBase
{
    private readonly ILogger<SsoController> _logger;

    private readonly IOktaClient _oktaClient;

    public SsoController(ILogger<SsoController> logger, IOktaClient oktaClient)
    {
        _logger = logger;
        _oktaClient = oktaClient;
    }

    [HttpGet]
    public async Task Sso([FromQuery] string name)
    {
        var idpProviders = await _oktaClient.IdentityProviders.ListIdentityProviders(q: name, limit: 1).ToListAsync();

        // search is case insensitive and "starts with", and not "exact match"
        var idp = idpProviders.FirstOrDefault(p => p.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase));

        if (idp == null)
        {
            _logger.LogWarning($"idp name '{name}' doesn't exist");

            var authenticationProperties = new AuthenticationProperties
            {
                RedirectUri = "/"
            };

            await HttpContext.ForbidAsync(authenticationProperties);
        }
        else
        {
            var properties = new AuthenticationProperties
            {
                RedirectUri = "/",
                Items = { ["idp"] = idp.Id }
            };
            await HttpContext.ChallengeAsync(properties);
        }
    }
}

When you hit /sso?name=MySamlIdp url you will look for MySamlIdp Identity Provider defined in step 1, and use its id to tell Okta where to redirect the user for authentication. The IOktaClient defined in Okta.Sdk nuget.