SSO get passcode programatically

Is there a way to get a passcode on behalf of a user programmatically?

What we have is a proxy that hits a management API to approve products for developer apps. We are using a proxy so that we can change the post into a get so a user can click a link to do the approval. When they click the link we send back a WWW-Authenticate header to prompt them to enter their Apigee credentials (which allows them to do the approval based on their role).

With SSO we can no longer pass in basic Auth credentials so we are wondering how to do this flow with SSO. Help would be very much appreciated.

3 15 714
15 REPLIES 15

Good question!

The documented way to do this is via the command-line tools that Apigee has released, like acurl, get_token and fetch_token; the tools are described here.

There is a section on that page specifically for how these tools work when you have configured SSO (which I suppose uses SAML). Look here.

Unfortunately the API is not well documented. If you can read through the bash script source, you can see what calls you need to make, and to which URLs.

To get you started, you will use a login base url that looks like this:

https://SSOZONE.login.apigee.com

...where SSOZONE is your own sso zone name.

You'll need to get a one-time "passcode" by sending a GET to LOGIN_BASE_URL/passcode . This will use your existing SAML assertion, or it will prompt you to authenticate via your SAML IdP if necessary.

With the passcode, you can get a token via a request like this:

curl -X POST LOGIN_BASE_URL/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d 'grant_type=password&response_type=token&passcode=PASSCODE'

I've put some effort into designing a JavaScript library suitable for using within nodejs that automates some of this. apigee-edge-js . If you can read JS, the source in edge.js shows how it acquires tokens.

I don't think we can programatically send a GET to LOGIN_BASE_URL/passcode because that redirects us to ping where we would authenticate. Does that make sense or am I misunderstanding your explanation?

Your understanding is correct. the GET to /passcode needs to present the SAML Assertion, already issued by Ping, in order to avoid the 302 redirect. If there is no SAML assertion then, I guess the call would get redirected to Ping (your SAML IdP), then the assertion is returned and you send it along to /passcode.

Unfortunately I don't know the API for /passcode, and I'm not sure how that assertion needs to be formatted in the request.

  • if in the Authorization header
  • if in a query param
  • something else?

Maybe trying it in a browser, with the Chrome Developer Tools, you can see what it looks like.

@Dino Do you know where I can get more information about the /passcode API I tried tracing it through the browser and I couldn't really understand it. It looks like there is some call to "/saml/SSO/alias/apigee-saml-login-opdk-sandbox" with the SAML assertion.

Do you think it would be possible to generate a SAML assertion as part of the proxy and make the call to /passcode to get a passcode and then call the management url with that passcode (all within one proxy)?

let me see if I can recruit some other people to clarify.....

@Dino-at-Google Any updates?

@Dino-at-Google Any updates on this? We are trying to get a passcode programatically

Let me ask again. . . I understand the request.

@Dino-at-Google Any updates?

My understanding is that people use "Service accounts".

Yah we have looked into that but the context we are using this is a CLI tool we have built which will download the proxy on behalf of the user so it seems more appropriate to be able to call the management API with their credentials instead of a service account.

@Dino-at-Google Sorry for the confusion. ^ is a different use case from the original post. We had two use cases for this. We solved the first one with a machine user but this CLI one seems like a machine user would be less appropriate.

I see! Well in that case, my opinion is that the correct user experience is to pop a browser window to allow the user to obtain the passcode.

It may sound like an awkward user experience, but maybe it isn't a significant issue. The CLI would need to pop a browser window only to obtain an access_token initially. (Not sure about the refresh token scenario). This will be rare. After acquiring the token, you have a refresh token, and you can use that to get a new token without using the /passcode.

So the method would be:

  • check the token stash (local on the machine) for an access_token for {User, Mgmt server} tuple. (Org does not matter)
  • If there is an access_token stashed, try to use it to check the org. (GET /v1/o/ORGNAME)
  • If you get 200 OK, then the access_token is good.
  • If you get 401, then the access_token is expired. Use the accompanying stashed refresh_token to try to get a new access_token. (I think refresh tokens for Apigee Edge SaaS have a lifetime of 7 days, although this is not documented AFAIK)
  • If 200 OK, then you now have a good access_token. Stash it, with the new refresh_token, keyed with the {user,mgmtserver} pair.
  • If 401, then the refresh token is expired. You need to pop the /passcode endpoint in a browser window to allow the user to authenticate.
  • Use the passcode to obtain a new {access_token, refresh_token}
  • Stash the newly acquired access_token and refresh_token, keyed with the {user,mgmtserver} pair.

There is a possibility that an existing access_token could expire during execution of the CLI. A savvy CLI would handle a token expiry event at any point during its execution. Or, one could imagine the CLI *always* getting a fresh token at the start of execution to minimize the chance of token expiry during execution.

Also you may want to consider race conditions - with there ever be multiple instances of the CLI operating on the machine at the same time? In which case you need to serialize writes to the token stash.

If you don't want any GUI at all, then I guess you will need to screen-scrape the /passcode endpoint. Apigee does not provide documentation for the HTML/Form interface for that endpoint. So you'd just examine the html page and reverse-engineer it. And keep in mind the /passcode endpoint may change, since it's not documented.

What are you using the build the CLI? (Maybe you told me but I've lost it now) . There is a JS library that does token management and stashing.

https://github.com/DinoChiesa/apigee-edge-js

The token stash file is just a JSON hash with various properties. Each property (key) is {user,mgmtserver} and the value of that key is the access token response.

{
  "dchiesa@google.com##https://api.enterprise.apigee.com": {
    "access_token": "eyJ...387efjkfeeew",
    "token_type": "bearer",
    "refresh_token": "eyJ...xRebGlFJtTaHOg",
    "expires_in": 1799,
    "scope": "scim.me openid password.write approvals.me oauth.approvals",
    "jti": "c70b82d2-4fee-4693-b66b-b667740fd5e1",
    "issued_at": 1529957227074
  }
}

This library does not yet have the feature to pop the /passcode endpoint in the case where the token is expired and a passcode is required. But we could easily add that. It would require reliance on opn.

Look here in the getNewToken function. I suppose it would conditionally pop a browser window there, depending on the user of a zone.

Thanks, this is super helpful I will explore this and see what I can learn. Yah our CLI is node.