I know this is pretty raw but it should get you past the challenge of MFA.
The model I use:
public class OktaAuthRequestInformation
{
public string Domain { get; set; }
public string OktaAuthorizationServer { get; set; }
public string ClientId { get; set; }
public string RedirectUrl { get; set; }
public string RedirectUrlEncoded => System.Net.WebUtility.UrlEncode(RedirectUrl);
public string ResponseType { get; set; } = System.Net.WebUtility.UrlEncode("id_token");
public string State { get; set; } = Guid.NewGuid().ToString();
public string Nonce { get; set; } = Guid.NewGuid().ToString();
public string Scope { get; set; } = System.Net.WebUtility.UrlEncode("openid email profile");
public string AuthnUri => $"{Domain}/api/v1/authn";
public string AuthorizeUri => $"{Domain}/oauth2/{OktaAuthorizationServer}/v1/authorize";
public string Username { get; set; }
public string Password { get; set; }
public string MultiFactorAuthenticationQuestionAnswer { get; set; }
}
The class the communicates with OKTA.
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using System.Web;
public static class OktaRequests
{
private const string _mediaType = "application/json";
private const string _idTokenKey = "id_token";
private const string _accessTokenKey = "access_token";
public static async Task<string> GetOktaToken(string domain, string authServer, string clientId, string redirectUrl, string userId, string password, string multiFactorAuthenticationQuestionAnswer)
{
var oktaAuthRequestInformation = new OktaAuthRequestInformation {
Domain = domain,
OktaAuthorizationServer = authServer,
ClientId = clientId,
RedirectUrl = redirectUrl,
Username = userId,
Password = password,
MultiFactorAuthenticationQuestionAnswer = multiFactorAuthenticationQuestionAnswer
};
StringContent stringContent = GetContentForOktaAuthNCall(oktaAuthRequestInformation);
HttpClientHandler httpClientHandler = new HttpClientHandler
{
AllowAutoRedirect = false
};
using (var httpClient = new HttpClient(httpClientHandler))
{
httpClient.DefaultRequestHeaders
.Accept
.Add(new MediaTypeWithQualityHeaderValue(_mediaType));
var result = await CallOKTAAuthN(httpClient, oktaAuthRequestInformation, stringContent);
return result[_idTokenKey];
}
}
public static async Task<Dictionary<string, string>> GetOktaIdTokenAndAccessToken(string domain, string authServer, string clientId, string redirectUrl, string userId, string password, string multiFactorAuthenticationQuestionAnswer)
{
var oktaAuthRequestInformation = new OktaAuthRequestInformation
{
Domain = domain,
OktaAuthorizationServer = authServer,
ClientId = clientId,
RedirectUrl = redirectUrl,
Username = userId,
Password = password,
MultiFactorAuthenticationQuestionAnswer = multiFactorAuthenticationQuestionAnswer
};
oktaAuthRequestInformation.ResponseType = System.Net.WebUtility.UrlEncode("token id_token");
StringContent stringContent = GetContentForOktaAuthNCall(oktaAuthRequestInformation);
HttpClientHandler httpClientHandler = new HttpClientHandler
{
AllowAutoRedirect = false
};
using (var httpClient = new HttpClient(httpClientHandler))
{
httpClient.DefaultRequestHeaders
.Accept
.Add(new MediaTypeWithQualityHeaderValue(_mediaType));
return await CallOKTAAuthN(httpClient, oktaAuthRequestInformation, stringContent);
}
}
private static StringContent GetContentForOktaAuthNCall(OktaAuthRequestInformation oktaAuthRequestInformation)
{
dynamic bodyOfRequest = new
{
username = oktaAuthRequestInformation.Username,
password = oktaAuthRequestInformation.Password,
options = new
{
multiOptionalFactorEnroll = true,
warnBeforePasswordExpired = true
}
};
var body = JsonConvert.SerializeObject(bodyOfRequest);
var stringContent = new StringContent(body, Encoding.UTF8, _mediaType);
return stringContent;
}
private static async Task<Dictionary<string, string>> CallOKTAAuthN(HttpClient httpClient, OktaAuthRequestInformation oktaAuthRequestInformation, StringContent stringContent)
{
HttpResponseMessage authnResponse = await httpClient.PostAsync(oktaAuthRequestInformation.AuthnUri, stringContent);
if (authnResponse.StatusCode == HttpStatusCode.Unauthorized)
{
throw new UnauthorizedAccessException("The user could not be authenticated.");
}
if (authnResponse.IsSuccessStatusCode)
{
var responseContent = await authnResponse.Content.ReadAsStringAsync();
var response = JsonConvert.DeserializeObject<OktaMfaResponse.ResultOfACall>(responseContent);
if(response.status == "MFA_REQUIRED")
{
var mfaResponse = JsonConvert.DeserializeObject<OktaMfaResponse.MfaRequiredResult>(responseContent);
responseContent = await CallOktaPasscodeVerifyForMFA(httpClient, mfaResponse._embedded.factors[0]._links.verify.href, mfaResponse.stateToken, oktaAuthRequestInformation.MultiFactorAuthenticationQuestionAnswer);
response = JsonConvert.DeserializeObject<OktaMfaResponse.ResultOfACall>(responseContent);
}
if (response.status != "SUCCESS")
{
throw new UnauthorizedAccessException($"Can't authenticate with OIDC {responseContent}");
}
var successfulResponse = JsonConvert.DeserializeObject<OktaMfaResponse.SuccessResult>(responseContent);
return await CallOktaAuthorize(httpClient, oktaAuthRequestInformation, successfulResponse.sessionToken);
}
throw new InvalidOperationException($"Something went wrong in {nameof(CallOKTAAuthN)}");
}
private static async Task<string> CallOktaPasscodeVerifyForMFA(HttpClient httpClient, string url, string stateToken, string multiFactorAuthenticationQuestionAnswer)
{
var payload = new
{
stateToken,
answer = multiFactorAuthenticationQuestionAnswer
};
var serialized = JsonConvert.SerializeObject(payload);
var body = new StringContent(serialized, Encoding.UTF8, _mediaType);
HttpResponseMessage authorizeResponse = await httpClient.PostAsync(url, body);
var responseContent = await authorizeResponse.Content.ReadAsStringAsync();
if (!authorizeResponse.IsSuccessStatusCode)
{
throw new UnauthorizedAccessException($"Can't authenticate with OIDC {responseContent}");
}
return responseContent;
}
private static async Task<Dictionary<string, string>> CallOktaAuthorize(HttpClient httpClient, OktaAuthRequestInformation oktaAuthRequestInformation, string sessionToken)
{
var authorizeUri = oktaAuthRequestInformation.AuthorizeUri + "?" +
$"client_id={oktaAuthRequestInformation.ClientId}" +
$"&redirect_uri={oktaAuthRequestInformation.RedirectUrlEncoded}" +
$"&response_type={oktaAuthRequestInformation.ResponseType}" +
$"&sessionToken={sessionToken}" +
$"&state={oktaAuthRequestInformation.State}" +
$"&nonce={oktaAuthRequestInformation.Nonce}" +
$"&scope={oktaAuthRequestInformation.Scope}";
HttpResponseMessage authorizeResponse = await httpClient.GetAsync(authorizeUri);
var statusCode = authorizeResponse.StatusCode;
if(statusCode == System.Net.HttpStatusCode.Found)
{
var redirectUri = authorizeResponse.Headers.Location;
var queryDictionary = HttpUtility.ParseQueryString(redirectUri.AbsoluteUri);
var idTokenKey = $"{oktaAuthRequestInformation.RedirectUrl}#{_idTokenKey}";
var idToken = queryDictionary[idTokenKey];
if (string.IsNullOrWhiteSpace(idToken))
{
throw new InvalidOperationException($"Something went wrong in {nameof(CallOktaAuthorize)}, redirect of {redirectUri}");
}
var result = new Dictionary<string, string>();
result.Add(_idTokenKey, idToken);
var accessToken = queryDictionary[_accessTokenKey];
if (accessToken != null)
{
result.Add(_accessTokenKey, accessToken);
}
return result;
}
throw new InvalidOperationException($"Something went wrong in {nameof(CallOktaAuthorize)}");
}
}