Unit Testing and Implicit Flow


#1

I am writing unit tests for our API. My UI is a Angular SPA so we are using the Implicit flow. We have a WebAPI backend.

I am writing integration tests which run in our continuous delivery pipeline.

How can I call the OKTA api rest endpoints with HttpClient and a testing userid and password so that I can retrieve a valid id_token or access_token to my API for integration testing?


Using sessionToken how can get access_token and id_token
#2

@vijet have any learnings here? :slight_smile:


Integration Testing and MFA
#3

@glenndorr, cc @tom -
I was discussing this problem with @lboyette & @jmelberg and we have the following suggestions -

Assumptions -

  • You want to test your backend APIs (protected by Okta Auth server) by passing it an Okta access_token
  • Your test code needs to call Okta APIs to get the access_token
  • You then pass that access_token to your backend and verify its behavior
  1. Instead of an implicit flow, for testing, can you use the client credentials flow? https://developer.okta.com/authentication-guide/implementing-authentication/client-creds.html

Pros -

  • You can directly get the access_token from /token endpoint by passing the client credentials
  • You only need to call the /token Okta API

Cons -

  • You will need to store the client_id & client_secret securely (Assuming you are using a CI that can store encrypted variables, you should be good)
  • You will need to create a new application in your okta org
  • Your resource server backend must be configured to accept an additional “test client_id” (Not entirely sure how to configure this)
  1. If you want to use the implicit flow, you will need to follow redirects from the /authorize endpoint and retrieve the access_token from the redirect URI (either as query parameter or hash fragment)

These are the steps you can follow -

https://developer.okta.com/docs/api/resources/oidc#authorize

  • Once you parse the redirect URI, you should have the access_token in your test code and use that to test your backend API.

Hope this helps!

-Vijet


#4

Not sure what language you’re writing your tests in, but here’s an example for Node.js:

const fetch = require('isomorphic-fetch');

const DOMAIN = 'yourOktaDomain';
const CLIENT_ID = 'sampleClientId';
const REDIRECT_URI = 'http://localhost:3000/implicit/callback'; // or your redirect_uri
const TEST_USERNAME = 'sampleUser';
const TEST_PASSWORD = 'samplePass';

(async () => {
  // Make a call to the authn api to get a sessionToken
  const authnRes = await fetch(`${DOMAIN}/api/v1/authn`, {
    method: 'POST',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      username: TEST_USERNAME,
      password: TEST_PASSWORD
    })
  });
  const trans = await authnRes.json();
  
  // Send the session token as a query param in a GET request to the authorize api
  const authorizeRes = await fetch(
    `${DOMAIN}/oauth2/default/v1/authorize?` +
    'response_type=token&' +
    'scope=openid&' +
    'state=TEST&' +
    'nonce=TEST&' +
    `client_id=${CLIENT_ID}&` +
    `redirect_uri=${REDIRECT_URI}&` +
    `sessionToken=${trans.sessionToken}`);

  // Parse access_token from url
  console.log(authorizeRes.url);
})();

#5

Thank you both, it worked! Here is the c# version for those that need it:

public class OktaRequests
{
    public static async Task<Tokens> GetOktaToken()
    {
        var domain = "your okta domain"; 
        var oktaAuthorizationServer = "your okta auth server (usually default)";
        var clientId = "your okta application client id";
        var redirectUrl = "your call back that you have set in OKTA";
        var redirectUrlEncoded = System.Net.WebUtility.UrlEncode(redirectUrl);
        var responseType = System.Net.WebUtility.UrlEncode("id_token token");
        var state = "testing";
        var nonce = "testing nonce";
        var scope = System.Net.WebUtility.UrlEncode("openid email profile");
        var authnUri = $"{domain}/api/v1/authn"; 
        var username = "your testing userid";
        var password = "your testing password";
        
        dynamic bodyOfRequest = new
        {
            username,
            password,
            options = new
            {
                multiOptionalFactorEnroll = true,
                warnBeforePasswordExpired = true
            }
        };

        var body = JsonConvert.SerializeObject(bodyOfRequest);

        var stringContent = new StringContent(body, Encoding.UTF8, "application/json");

        string sessionToken;

        HttpClientHandler httpClientHandler = new HttpClientHandler();
        httpClientHandler.AllowAutoRedirect = false;

        using (var httpClient = new HttpClient(httpClientHandler))
        {
            httpClient.DefaultRequestHeaders
                .Accept
                .Add(new MediaTypeWithQualityHeaderValue("application/json"));

            HttpResponseMessage authnResponse = await httpClient.PostAsync(authnUri, stringContent);

            if (authnResponse.IsSuccessStatusCode)
            {
                var authnResponseContent = await authnResponse.Content.ReadAsStringAsync();
                dynamic authnObject = JsonConvert.DeserializeObject(authnResponseContent);
                sessionToken = authnObject.sessionToken;

                var authorizeUri = $"{domain}/oauth2/{oktaAuthorizationServer}/v1/authorize?client_id={clientId}&redirect_uri={redirectUrlEncoded}&response_type={responseType}&sessionToken={sessionToken}&state={state}&nonce={nonce}&scope={scope}";

                HttpResponseMessage authorizeResponse = await httpClient.GetAsync(authorizeUri);
                var statusCode = (int)authorizeResponse.StatusCode;

                if(statusCode == System.Net.HttpStatusCode.Found)
                {
                     var redirectUri = authorizeResponse.Headers.Location;
                     var queryDictionary = HttpUtility.ParseQueryString(redirectUri.AbsoluteUri);
                     var idTokenKey = $"{oktaAuthRequestInformation.RedirectUrl}#id_token";

                    return new Tokens
                    {
                        idToken = queryDictionary[idTokenKey],
                        accessToken = queryDictionary["access_token"]
                    };
                }
            }
        }
        throw new Exception("Something went wrong");
    }
}