Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement OAuth as its now in Domoticz Beta #679

Open
galadril opened this issue Jan 12, 2023 · 15 comments
Open

Implement OAuth as its now in Domoticz Beta #679

galadril opened this issue Jan 12, 2023 · 15 comments
Assignees

Comments

@galadril
Copy link
Contributor

More info about OAuth:
https://github.com/domoticz/domoticz/blob/development/SECURITY_SETUP.md

@galadril galadril self-assigned this Jan 12, 2023
@kiddigital
Copy link

kiddigital commented Jan 12, 2023

Flow and pseudo code (WIP):

  • User creates an 'Application' in Domoticz (for example 'Domoticz4Android') and de-select 'Is Public'. Specify a 'client_secret' (a password for the Application)

  • User creates a Domoticz User that will be used from the App (or uses an already existing User). It is not recommended to use the standard 'domoticz' admin user for this.

  • Users starts (Android) App and configures it with

    • IP-address or URL (and port) of the Domoticz instance to connect to
    • The 'Application' name to use as part of the Authentication and Authorization process (called 'ClientID' in OAuth2 terms)
    • The 'client secret' for this Application
  • Users initiates the connection process

  • First the app queries /.well-known/openid-configuration to request the current end-points, methods, etc. supported by Domoticz. This information will be used in the next steps.

  • The app sends the User via a Browser with a GET request to /oauth2/v1/authorize endpoint (= authorization_endpoint) with multiple (url encoded) parameters:

    • client_id: the name of the Application as provided in the settings
    • response_type: set to code
    • redirect_uri: the URI to where the client will be redirected to. This should be a local 'webserver' on the device. For example 'https://127.0.0.1:8080'
    • scope: this should be openid
    • state: a random identifier that will be used to check if the response is specific for this request
      For additional security, the PKCE extension should be used by adding 2 more parameters
    • code_challenge_method: only S256 is accepted
    • code_challenge: a generated code verifier
      code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))
  • The User will be presented with a Login dialog (currently just a standard Browser auth dialog)

  • If authenticated correctly, the User (browser) will be redirected (HTTP 302) back to the provided location of the 'redirect_uri'

  • This redirect response should be captured by the App and the different URL parameters have to be processed

    • The 'state' identifier should be checked to see if it is the same and thus correct request
    • The 'code' identifier in URL is the temporary code that will be exchanged for a token in the next step
  • To exchange the received 'code' for tokens, a POST request needs to be done by the App itself (and not the Browser) to /oauth2/v1/token (= token_endpoint) and several parameters have to be provided:

    • code: the received code
    • grant_type: is authorization_code
    • client_id: same as before
    • client_secret: the 'client secret' from the Application created in Domoticz
    • redirect_uri: same as before
    • code_verifier: the original 'code verifier' used before to create the code_challenge from
  • If everything matches (the code_verifier and the client_id/client_secret), domoticz generates the tokens. The resulting JSON should contain a (JWT) token and an refresh_token.

Now this Bearer token should be provided with each request between the App and Domoticz using the 'Authorization' header.

Once the token has expired (receiving 401 from Domoticz), the refresh token can be used to request a new token without the need to go through the above process and asking the User for credentials again.

  • To do this, a POST request to the same token endpoint has to be done with the following parameters:
    • grant_type: is refresh_token
    • refresh_token: the refresh token received earlier

@galadril
Copy link
Contributor Author

galadril commented Jan 13, 2023

I could be wrong, but isnt the whole purpose of having a oauth pkce flow to not ask the user for credentials yourself from within the app. I would expect to open a browser with some parameters (challenge etc) to let Domoticz handle the authentication.

And then use the access/refresh tokens afterwards

Thats how i understand it from for example Okta
https://developer.okta.com/docs/guides/implement-grant-type/authcodepkce/main/

@kiddigital
Copy link

That is indeed how it works on the web. And if you 'sent' the user to Domoticz, it will ask for a User/Pass (currently through standard Basic Auth dialog of the browser and not via a nice login dialog at the moment) and redirect the User back.

I am not an App developer, I do not know if/how an App could send the User to their Domoticz instance, perform the login there and then get redirect back to the App. But I know it can be done as I see it work in multiple apps.

But you are right, ideally the App does not require to ask for User/Pass and just receives the 'code' and can continue from there. Let's make that work! And I am happy to help where I can.

@galadril
Copy link
Contributor Author

galadril commented Jan 15, 2023

So i can ask the user to fill in the port/ip for remote and local domoticz connections for example.

First the app will then generate a code verifier followed by a code challenge (for the pkce challenge)
Then open a browser to allow for user authentication with the next url;

https://{host}:{port}/authorize?
    response_type=code&
    code_challenge={codeChallenge}&
    code_challenge_method=S256&
    client_id=YOUR_CLIENT_ID&
    redirect_uri={yourCallbackUrl}&
    scope=SCOPE&
    state={state}

if the user is authenticated, domoticz login form is going to redirect to the {yourCallbackUrl} adding some authenticationToken parameter and the same state value as the original input:

{yourCallbackUrl}?code={authorizationCode}&state={state}

image

After authenticating, show a permissions acknowledgement form, Domoticz asking the user if you allow that application to connect
image

Then i could use this authorizationCode to get an access and refresh token via some POST call like:

curl --request POST \
  --url 'https://{host}:{port}/oauth/token' \
  --header 'content-type: application/x-www-form-urlencoded' \
  --data grant_type=authorization_code \
  --data 'client_id=YOUR_CLIENT_ID' \
  --data 'code_verifier={yourGeneratedCodeVerifier}' \
  --data 'code={yourAuthorizationCode}' \
  --data 'redirect_uri={yourCallbackUrl}'

That token endpoint checks the PKCE code challenge and verifier.. also the redirect url should be the same as the original requested one and validates the client id.

If all goes well, you'll receive an HTTP 200 response with a payload containing access_token, refresh_token, id_token, and token_type values:

{
  "access_token":"eyJz93a...k4laUWw",
  "refresh_token":"GEbRxBN...edjnXbL",
  "id_token":"eyJ0XAi...4faeEoQ",
  "token_type":"Bearer",
  "expires_in":86400
}

If i need to refresh my access token, it would be something like:

curl --request POST \
  --url 'https://{host}:{port}/oauth/token' \
  --header 'content-type: application/x-www-form-urlencoded' \
  --data grant_type=refresh_token \
  --data 'client_id=YOUR_CLIENT_ID' \
  --data 'refresh_token={yourRefreshToken}'

a sample of a yourCallbackUrl would be for the mobile app:
domoticz://app

that redirect link has a deeplink back the app in.

This is how im used to implementing PKCE / OAUTH flows

@kiddigital
Copy link

Yes, that is correct :)

Domoticz internal OAuth2/OIDC service does not have such nice (2-step) login/consent screens yet, but I am working on a (single-step) login/consent screen.. That will be available soon in an upcoming Domoticz Beta.

But everything else is already there and can be implemented. Just currently the browser does not show a nice login but the standard Basic Auth dialog. But that is not relevant to complete the App side of the flow.

If I can help somehow with testing or ... let me know.

@kiddigital
Copy link

kiddigital commented Jan 16, 2023

@galadril , I updated my initial comment with the Flow/Pseudo code...

The 'client_id' AND the 'client_secret' are both needed. Although using PKCE ensures the code exchange is not compromised, it does not authenticate the client application (more on this can be found here). And the 'client_secret' has to be provided when doing the POST to the token endpoint. This way the specific app instance gets a trust relation with the specific Domoticz instance.

Don't forget to query the /.well-known/openid-configuration first to get the correct URI's for the Authentication and Token endpoints. Somewhere in the future, Domoticz users can configure different URI's if they are using another IAM service instead of the now build-in one.

@galadril
Copy link
Contributor Author

@kiddigital Did the new Domoticz update bring your oAuth to the stable release??

@kiddigital
Copy link

kiddigital commented Feb 16, 2023

Yes! 😁 Give it a go..

@galadril
Copy link
Contributor Author

I will.. but yeah moving houses, Android Auto support and Wear OS stopped working came in between..

@galadril
Copy link
Contributor Author

@kiddigital while going throught this, i just got the idea.. why shouldn't we have a build-in client id/secret for the mobile app?
As its part of Domoticz, just like the frontend.. its kinda weird for me to ask from the official Domoticz apps, to first setup the app right?

@kiddigital
Copy link

Makes sense indeed.. saves the User a step.

@galadril
Copy link
Contributor Author

Can you provide me a build-in id/secret?

@kiddigital
Copy link

kiddigital commented Feb 28, 2023

Can you provide me a build-in id/secret?

There is no really build-in id/secret, just entries in the Applications table.

Just create an Application called 'DomoticzMobileApp' with a random secret (7fc692bdd8adaadb2d77b91638770999 for example).

A few default entries will be there by default (disabled except the standard UI) including 'DomoticzMobileApp'.

But the User has to explicitly activate it AND set a secret (either private or public key-pair). Otherwise any app could fake it is a default app and only need User/Pass.

The default apps just make it easier (no typos in ClientID for example).

@galadril
Copy link
Contributor Author

galadril commented Jul 4, 2023

Domoticz internal OAuth2/OIDC service does not have such nice (2-step) login/consent screens yet, but I am working on a (single-step) login/consent screen.. That will be available soon in an upcoming Domoticz Beta.

was this done already btw?

@kiddigital
Copy link

Yes, there is an optional field for the 2FA code for Users that have 2FA enabled.

And by default there is an 'domoticzMobileApp' application available in the 'Applications' config which is disabled by default and has to be enabled.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants