Unit Testing and Implicit Flow

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?

1 Like

@vijet have any learnings here? :slight_smile:

@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

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);
})();

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");
    }
}
3 Likes

Based on the two posts I made a similar request in Java. Thanks to the input from theses posts, I managed to create something that works with Spring Boot.

I tried this out and it worked, what I want to do next is saving the token in database and checking if it’s valid so that I do not need to do a new request for every test.

I use this token for my in testing in Spring boot. The Junit test’s will create a in memory database and start the application and then I will call the endpoint to get a access token so that I can send it to my secured endpoints and confirm that everything is working properly.

public String createToken() {
        final String DOMAIN = "https://{oktaDomain}.com/";
        final String CLIENT_ID = "{clientId}";
        final String REDIRECT_URI = "{redirectUri}";
        final String USERNAME = "username@mail.com";
        final String PASSWORD = "password";

        final EntityLogin entityLogin = new EntityLogin(USERNAME, PASSWORD);

        final HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        final WebTarget authentication = ClientBuilder.newClient().target(DOMAIN + "/api/v1/authn");
        final Response response = authentication
            .request(String.valueOf(MediaType.APPLICATION_JSON))
            .post(Entity.json(entityLogin));

        final String[] responseFromAuthentication = response.readEntity(String.class).split(":");
        final String sessionToken = responseFromAuthentication[5].subSequence(1, 56).toString();

        final WebTarget authorization = ClientBuilder.newClient()
            .target(DOMAIN + "/oauth2/default/v1/authorize")
            .queryParam("response_type", "token")
            .queryParam("scope", "openid")
            .queryParam("state", "TEST")
            .queryParam("nonce", "TEST")
            .queryParam("client_id", CLIENT_ID)
            .queryParam("redirect_uri", REDIRECT_URI)
            .queryParam("sessionToken", sessionToken);

        final String accessToken = authorization.request().get().getLocation().getFragment().substring(13);
        return accessToken.substring(0, accessToken.indexOf("&"));
    }

    
    private class EntityLogin {
        private String username;
        private String password;
    }