smallstep / cli

🧰 A zero trust swiss army knife for working with X509, OAuth, JWT, OATH OTP, etc.
https://smallstep.com/cli
Apache License 2.0
3.66k stars 254 forks source link

step oauth --console options and custom redirect_uri #619

Open tommyli opened 2 years ago

tommyli commented 2 years ago

I'd like to ask if there's a way to customise the redirect_uri parameter when --console is enabled? Right now when --console is used, redirect_uri is always set to urn:ietf:wg:oauth:2.0:oob. This I think is a Google implementation rather than part of the OAuth2.0 spec?

My use case is for a CLI application to be run on a server (so headless with no browser, i.e. can't open a browser and redirect to a localhost listen port). Below command is what I'm trying to get working with Okta. I added urn:ietf:wg:oauth:2.0:oob as an accepted "Sign-in redirect URIs" in the Okta application.

step oauth \
  --provider https://okta.domain.com \
  --client-id someclientid \
  --client-secret someclientsecret \
  --console \
  --scope="openid profile email" \
  --bare --oidc

After logging in, I get the below error in the browser console.

Failed to launch 'urn:?code=X9tNq2V2TAvRFboPLZ9CUcPm_WRi-I4xhD2cAgN5KbA&state=LQ95qbURePdXfQlpxIhBo3bXUbUNQuJj' because the scheme does not have a registered handler.

So it seems the Okta side is mostly working and fails on the redirect.

Of course, manually copying and pasting that code to the step oauth "Enter verification code:" command prompt does work but it'll be nice if it can redirect to a customisable URL (where I can implement a simple web page to display the auth code nicely). Are there any security concerns with this? Although I don't see this as any more or less secure than using urn:ietf:wg:oauth:2.0:oob and displaying the code in a browser ready for copy and paste.

maraino commented 2 years ago

It's possible to change the redirect_uri parameter with the flag --listen-url. We used this name to avoid confusion with the existing --redirect-url, and because is commonly used with the --listen flag.

The idea behind this flag is to be able to get tokens over an SSH connection or even a docker container:

$ ssh -L 10000:localhost:10000 my.server
my.server:~$ step oauth --listen :10000 --listen-url http://127.0.0.1:10000 --oidc --bare
Cannot open a web browser on your platform.

Open a local web browser and visit:

https://accounts.google.com/o/oauth2/v2/auth?....

Then use the browser in your own machine to do the OAuth flow, and because we're redirecting the port 10000 to the server, you can do the full flow.

With docker is similar but doing:

run -it -p 10000:10000 smallstep/step-cli:latest /bin/bash

If you want to combine it with step ca certificate you can do it like:

$ docker run -it -p 10000:10000 smallstep/step-cli:latest /bin/bash
bash-5.1$ step ca bootstrap --ca-url ...  --fingerprint ...
bash-5.1$ export STEP_LISTEN=0.0.0.0:10000
bash-5.1$ export STEP_LISTEN_URL=http://127.0.0.1:10000
bash-5.1$ step ca certificate mariano@smallstep.com mariano.crt mariano.key
tommyli commented 2 years ago

Thanks for that. Then it seems there's a bug with the --console option? My understanding is that the --console option is to force "out of band" mode, i.e. same as step-cli detecting that it "Cannot open a web browser on your platform." E.g.

This works as you mentioned above.

step oauth \
  --listen-url http://firefire.co \
  --scope="openid profile email" \
  --bare --oidc

Cannot open a web browser on your platform.

Open a local web browser and visit:

https://accounts.google.com/o/oauth2/v2/auth?client_id=1087160488420-8qt7bavg3qesdhs6it824mhnfgcfe8il.apps.googleusercontent.com&code_challenge=ddDSNmxorgfDpQVLoVpcjGdsQnd5dj7hmXHG-_st174&code_challenge_method=S256&nonce=deb8a87276900b5ef4cce128341ca0673d73a47c7b1649e47068289eed5835be&redirect_uri=http%3A%2F%2Ffirefire.co&response_type=code&scope=openid+profile+email&state=OtEvcIOSGwC5TlK1sGyFF6mrUqZsaqn1

but this fails, or at least the redirect_uri is not what I'm expecting.

step oauth \
  --console \
  --listen-url http://firefire.co \
  --scope="openid profile email" \
  --bare --oidc

Open a local web browser and visit:

https://accounts.google.com/o/oauth2/v2/auth?client_id=1087160488420-8qt7bavg3qesdhs6it824mhnfgcfe8il.apps.googleusercontent.com&code_challenge=lRFfxfHqfjdy7ik3HCC_lmtJ6g5hhQs3FQJMGmEoxXU&code_challenge_method=S256&nonce=89914605638fde6205c04d910f5b2f9200ffc9ab2b7b35e961343b5f2c792d54&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&response_type=code&scope=openid+profile+email&state=1KPF8E9XfqO4RzXWQBBbJxDoYn9rECHh

Enter verification code:
maraino commented 2 years ago

--console and --listen-url cannot be used together, they are used in two different OAuth flows.

The console flag always uses redirect_uri=urn:ietf:wg:oauth:2.0:oob, and I don't see the need to use a different one because there's no redirect. In that flow, you get a code that you enter that is used to authenticate your app.

The only way we have right now to simulate that oob flow with okta is the one I mentioned before where you redirect the ports to your server to finalize the flow.

sbeaulie commented 2 years ago

I was able to get the code with okta, but without the --console there is nowhere to input the verification code? What am I missing?

For okta provider

step oauth --listen :10000 --listen-url http://127.0.0.1:10000 --oidc --bare --provider https://myown.okta.com/.well-known/openid-configuration --client-id REDACTED

Open a local web browser and visit:
REDACTED

<-- nothing here to input

The issue is that I do not get the stdin prompt for Enter verification code: @tommyli did you manage to make that work without --console?

I also didn't use the client-secret (contrary to the original post) as I do not want to provide it to our users, can step oauth be used without?

maraino commented 2 years ago

@sbeaulie Okta does not support the out-of-band flow, that is the one that --console use.

But if you're trying to get a token on a computer that doesn't have a browser, like a container or a server using SSH, you can combine --listen (and in some cases --listep-url) flags with the port redirection as I'm showing in my previous comment https://github.com/smallstep/cli/issues/619#issuecomment-1027510449

maraino commented 2 years ago

I also didn't use the client-secret (contrary to the original post) as I do not want to provide it to our users, can step OAuth be used without?

There are some OIDC providers that do not use a client secret, but if yours has one it is usually required to exchange the authorization code for the access_token or id_token.

tommyli commented 2 years ago

I was able to get the code with okta

@sbeaulie - how did you get this code from okta when it doesn't support out-of-band flow?

Thanks to @maraino for explaining, I think I understand how --console is intended to work. For IDPs that do support OOB flow, they would detect an oob flow because from the URL param redirect_uri=urn:ietf:wg:oauth:2.0:oob and upon login, instead of redirecting to your web application, the IDP would redirect to its own web servers and display the code for you to paste in some console. It's a pity Okta does not support this and always redirects to what you provide in redirect_uri param. I did ask above whether I should raise a feature request where even for --console, I can override the fixed redirect_uri=urn:ietf:wg:oauth:2.0:oob and redirect to somewhere of my choice, namely a web server I control and can display the response code in a user friendly page.

Since we're using Okta also, we're using @maraino suggestion to create an SSH tunnel to the remote server that requires the oauth login. This is not ideal for us because get our users to login like that can be a bit cumbersome.

There are some OIDC providers that do not use a client secret

Did you mean Authorization Code flow with PKCE? If so, then step-cli already supports this. I've tested this also and it works.

maraino commented 2 years ago

@tommyli

... I did ask above whether I should raise a feature request where even for --console, I can override the fixed redirect_uri=urn:ietf:wg:oauth:2.0:oob and redirect to somewhere of my choice, namely a web server I control and can display the response code in a user friendly page.

I'm not sure if you're talking about IdPs that support OOB or the ones that do not.

For the ones that do support OOB, they will just show the code and will not redirect to our app, so there's no point in using a custom web.

For the ones that do not support OOB, what would be the difference between just not using the --console flag and using the --listen-url. That flag allows you to redirect to a URL of your choice, but unless it's a tunnel, that service won't be able to properly validate the request.

When the IdP redirects to you, in the query you will see two parameters code and state. The code is one of the parameters sent to the token endpoint to get the access_token, id_token, ... The state is a random string step added to the auth endpoint, the one that you open in your browser, and you should validate it to verify the response matches your request. And with PKCE, you also need to send the code_verifier in plain text, the auth URL only hash a hash of it. See https://www.oauth.com/oauth2-servers/pkce/authorization-code-exchange/

Right now step doesn't have a flag to disable PKCE, a way to do it might make sense to add (with an --insecure flag). So without PKCE, and a way to validate the state, a form for example, you can use a URL of your choice. But at this point, I'm wondering if it just makes sense to build the full flow in that web.

Did you mean Authorization Code flow with PKCE? If so, then step-cli already https://github.com/smallstep/cli/issues/187 this. I've tested this also and it works.

Perhaps, PKCE code challenge might be a requirement for those IdPs that supports the flow without a client secret.