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

Hi,
@tom @vijet @lboyette
We are looking for something similar, except that we have a Web Application where we want to pass the session token to the /authorize call for each user.

Here are the specifics and the problem we are running into:

  1. We have a Web Application portal into which users login(not using okta’s login page, just an application login page).
  2. We have created an App Integration in Okta which is having Application Type as ‘Web Application’.
  3. We capture the user email and password from the front end and pass it to server side and are making an Okta API call to the Authentication API and receiving a sessiontoken back.
    Till here things are good.
    Now the new requirement is to : Get user specific access token in the server side code and pass it as a JWT token to another API.
    What we tried doing:
  4. Make a call to the /authorize endpoint, passing the session token(and other relevant parameters)
    Issue we are running into:
    It is showing the Okta Login page again and if we set prompt=none, then it says user is not signed in.
    Since the user was already authenticated in the previous step using authentication API, we don’t want them to have to put in credentials again.
    Is there a way to avoid that?

Note 1:
Our understanding is we cannot use the Client Credentials Flow in our case, because then the access token received back is not for the user logged in, rather for the application, please correct if this is a wrong assumption.
Note 2:
We did find an option where we set the Application Type as ‘Native Application’ and use the Resource Owner Password grant type, so in that approach, for /authorize endpoint call we need to pass the user email and password.
But we didn’t want to pass those if possible for security reasons and also we didn’t want to switch from the existing ‘Web Application’ in Okta to ‘Native Application’. (The reason we had to choose ‘Native Application’ is because the Resource owner password grant type is not an option for ‘Web Application’.)

Any suggestions or directions on this would be great!

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.