diff --git a/docs/authentication/oidc/keycloak.md b/docs/authentication/oidc/keycloak.md new file mode 100644 index 00000000..1e74bf19 --- /dev/null +++ b/docs/authentication/oidc/keycloak.md @@ -0,0 +1,101 @@ +# Keycloak + +This guide shows how to setup Papermerge + [Keyloak](https://keycloak.org) as OIDC identity +provider. It was tested with Keycloak 24.0.2. + +To follow this guide you need one {{ extra.project }} and one Keycloak instance. +For this guide we have: + +- http://keycloak.trusel.net:8080/ (Keycloak instance) +- http://demo.trusel.net:12000/ ({{extra.project}} instance) + +Of course for your specific deployment you'll want to serve both apps over +https, with valid certificates and without featuring ports in URLs, but for +our guide we will skip those parts. + +First we will configure Keycloak, and then we will start {{ extra.project }} +with correct environment variables. + +## Step 1 - Create Realm + +Create a new realm in Keycloak as described [here](https://www.keycloak.org/getting-started/getting-started-docker#_create_a_realm). We will name it "myrealm". + + +## Step 2 - Create User + +In "myrealm" create a user, as described [here](https://www.keycloak.org/getting-started/getting-started-docker#_create_a_user) with following details: + + - username: bender + - email: bender@mail.com + - password: benderpass + +User "bender" will be administrative user in {{ extra.project }}. +Let's create OIDC client. + +## Step 3 - Create OIDC Client + +Make sure you are currently in "myrealm". +Click Clients -> Create client. +Choose: + + - Client type: OpenID Connect + - Client ID: papermerge + - Client authentication: "ON" + - Home URL: http://demo.trusel.net:12000 + - Valid redirect URIs: http://demo.trusel.net:12000/oidc/callback + +Click "Save" + +Now, with OIDC Client saved, you can go to it's "Credentials" tab and +note its "Client Secret". You will need it in next step. +For this guide, the Client Secret is: + + - Client Secret: OHGMBgyAjcvDtn4PAu8w8vE9yf06aHn1 + + +## Step 4 - Configure "username" Claim + +... by default JWT token does not contain "username" claim ... + + +## Step 5 - Start Papermerge + +Now, start {{ extra.project }} with OIDC enabled, with following docker compose: + +```yaml +version: "3.9" + +x-backend: &common + image: papermerge/papermerge:{{extra.docker_image_version}} + environment: + PAPERMERGE__SECURITY__SECRET_KEY: super-secret-12345 + PAPERMERGE__AUTH__USERNAME: bender + PAPERMERGE__AUTH__EMAIL: bender@mail.com + PAPERMERGE__AUTH__PASSWORD: 1234-not-relevant-but-still-needs-to-be-here + PAPERMERGE__AUTH__OIDC_CLIENT_SECRET: OHGMBgyAjcvDtn4PAu8w8vE9yf06aHn1 + PAPERMERGE__AUTH__OIDC_CLIENT_ID: papermerge + PAPERMERGE__AUTH__OIDC_AUTHORIZE_URL: http://keycloak.trusel.net:8080/realms/myrealm/protocol/openid-connect/auth + PAPERMERGE__AUTH__OIDC_ACCESS_TOKEN_URL: http://keycloak.trusel.net:8080/realms/myrealm/protocol/openid-connect/token + PAPERMERGE__AUTH__OIDC_USER_INFO_URL: http://keycloak.trusel.net:8080/realms/myrealm/protocol/openid-connect/userinfo + PAPERMERGE__AUTH__OIDC_LOGOUT_URL: http://keycloak.trusel.net:8080/realms/myrealm/protocol/openid-connect/logout + PAPERMERGE__AUTH__OIDC_REDIRECT_URL: http://demo.trusel.net:12000/oidc/callback +services: + web: + <<: *common + ports: + - "12000:80" + worker: + <<: *common + command: worker +``` + +Note that `PAPERMERGE__AUTH__OIDC_CLIENT_SECRET`, `PAPERMERGE__AUTH__OIDC_CLIENT_ID` should match +their counterpart from step 3. + +`PAPERMERGE__AUTH__USERNAME` and `PAPERMERGE__AUTH__EMAIL` should match the user we created in step 2. As it was +mentioned, we will use "bender" as administrative user in {{extra.project}}. +Note that you need to specify `PAPERMERGE__AUTH__PASSWORD`, but whatever you put there is completely irrelevant +because administrative user will login with password managed in Keyloak (in our example it is "benderpass"). + +`PAPERMERGE__AUTH__OIDC_REDIRECT_URL` should match "Valid redirect URIs" from Step 3 and it should be of +format: `[http|https]:///oidc/callback`. diff --git a/docs/authentication/oidc/overview.md b/docs/authentication/oidc/overview.md new file mode 100644 index 00000000..99ad25f3 --- /dev/null +++ b/docs/authentication/oidc/overview.md @@ -0,0 +1,110 @@ +# Overview + +OpenID Connect (OIDC) is an interoperable authentication protocol based on the OAuth +2.0 framework of specifications. + +It is usual for organizations to use multiple software applications. Even a +small home lab features dozens of different apps. + +Imagine a small home lab with 7 apps and each of those app uses separate +authentication system. This means that the user of the home lab will need to +keep 7 separate accounts: 7 separate users and 7 different passwords! + +Of course users will use various tricks to make their life easier, like register +in all 7 accounts with same username and password. But still, they will need +to sign in daily - 7 times :). + +Sticking with same home lab example, wouldn't it be great to have one +single account with which user can sign in once and it will +take effect across all 7 apps? In other words, wouldn't it be great +to use single account for all application within home lab/organization/company ? + +Enter OIDC. + +OIDC solves above mentioned problem of multiple accounts hell. + +With OIDC, the accounts registration, authentication, accounts management +(e.g. password management), is offloaded from the shoulders of the app to +separate entity - **identity provider**. + +![One Identity Provider](../../img/auth/oidc/one-ip-many-apps.svg) + +!!! Note + + "Authenticate" means same as "sign in", "login". + Verbs "authenticate", "sign in", "login" are used interchangeably + + +With OIDC, instead of authenticating in each individual app, user +authenticates with on identity provider (IP) side. To authenticate users, +identity provider can employ various schemes like 2FA (two factor +authentication). Once authentication is successful on the identity provider +side, IP sends {{ extra.project }} a digitally signed token. All the +subsequent requests to {{extra.project}} need to have that token, or otherwise +they will be denied as not authorized. + +It may sounds abstract, because it is abstract. +I think couple of illustration will clear the waters. + + +First, in order to authenticate, OIDC enabled app ({{ extra.project }}) will +redirect user to OIDC provider sign in page. On successful authentication +{{ extra.project }} receives a token - so called jwt token. + +![One Identity Provider](../../img/auth/oidc/auth-process.svg) + +In above illustration, for step 1 -> 2 to work `PAPERMERGE__AUTH__OIDC_AUTHORIZE_URL` setting +is employed. For step 2 -> 3 to work `PAPERMERGE__AUTH__OIDC_REDIRECT_URL` is used. + +The trophy, which {{ extra.project }} receives from identity provider for +successful sign in, is so call called [JWT token](https://jwt.io/). Users +have no idea (and rightfully so) about JWT tokens, as all token business +happens behind the scenes. + + +!!! Note + + All processes described from here on, are not visible to the users. It all + happens behind the scenes for them. Information which follows is + meant for devs, DevOps, SREs. + +Your OIDC application needs JWT token as prove of +successful authentication. As you may guess, JWt token will be carried inside +each subsequent http requsts as http header. + +All incoming http requests are proved for validity of JWT token. If http request +has valid JWT token - request is permitted to reach app. If http request does not +contain valid JWT token - it is denied access to the app. + + +!!! Note + + By "valid JWT token" is usually meant that it contain valid digital signature, + it is not expired and maybe some other checks specific to identity provider. + + +Following illustrations depict what happens with incoming requests: + + +![HTTP Request Access Granted](../../img/auth/oidc/request-access-granted.svg) + +![HTTP Request Access Denied](../../img/auth/oidc/request-access-denied.svg) + +Inside {{ extra.project }} there is a "guard" which, for every incoming +request, asks identity provider if respective HTTP request is valid or not. +If identity provider says that JWT token is valid - request is permitted to pass, +otherwise access is denied. +This step is possible due to `PAPERMERGE__AUTH__OIDC_INTROSPECT_URL` setting. +OIDC introspect endpoint is go to endpoint to inquiry for validity +of the JWT token. + +The last technical detail to clarify, with high risk of diving in too many details, +is: what is this "guard" thingy? +"Guard" is nginx's +[authorization based on sub-request result](https://nginx.org/en/docs/http/ngx_http_auth_request_module.html). +In other words, there is nginx's "auth_request" for every incoming HTTP request and +depending on it's response's status code the request is allowed to pass or not. + + +That's all with OIDC theory. Now it is time to jump into +specific examples. First example is [Papermerge + Keycloak](keycloak.md) as identity provider. diff --git a/docs/img/auth/oidc/auth-process.svg b/docs/img/auth/oidc/auth-process.svg new file mode 100644 index 00000000..1b37f195 --- /dev/null +++ b/docs/img/auth/oidc/auth-process.svg @@ -0,0 +1,335 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + OIDC ennabled APP's login screen + + + Click "Login with OIDC" button + 1 + + + + 2 + 3 + User is redirectedto Identity ProviderSign in screen + Identity Provider Sign in screen.It may involve 2FA + + + On successful sign inuser is redirected back toapp together with so called"jwt token". + + diff --git a/docs/img/auth/oidc/one-ip-many-apps.svg b/docs/img/auth/oidc/one-ip-many-apps.svg new file mode 100644 index 00000000..d9c29e3f --- /dev/null +++ b/docs/img/auth/oidc/one-ip-many-apps.svg @@ -0,0 +1,225 @@ + + + + + + + + + + + + I am Papermerge AppI take care only of my own businesswhich is digital archives management + + + + I am Time Tracking AppI take care only of my own businesswhich is keep track of employees time + + + + I am Git Repository AppI take care only of my own businesswhich is storing git repositories + + I am Identity ProviderI manage user accounts,password, registrations,sign in, sign out. + Identity Provider (e.g. Keycloak) + Apps + + diff --git a/docs/img/auth/oidc/request-access-denied.svg b/docs/img/auth/oidc/request-access-denied.svg new file mode 100644 index 00000000..c7189b21 --- /dev/null +++ b/docs/img/auth/oidc/request-access-denied.svg @@ -0,0 +1,366 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Guard + + Identity Provider + Hey, Identity Provider, can youplease tell me if this request hasvalid JWT token? + + No, JWT token is NOT valid! + Sorry, request Ry, you can't pass + Ry + Ry + + Papermerge Core,UI, REST API + + Inside Papermerge docker container + + Ry = Some random HTTP request + + diff --git a/docs/img/auth/oidc/request-access-granted.svg b/docs/img/auth/oidc/request-access-granted.svg new file mode 100644 index 00000000..9175bfb5 --- /dev/null +++ b/docs/img/auth/oidc/request-access-granted.svg @@ -0,0 +1,366 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Guard + + Identity Provider + Hey, Identity Provider, can youplease tell me if this request hasvalid JWT token? + + Yes, JWT token is valid + OK, request Rx, you can pass + Rx + Rx + + Papermerge Core,UI, REST API + + Inside Papermerge docker container + + Rx = Some random HTTP request + + diff --git a/docs/settings/auth.md b/docs/settings/auth.md index b02fdef5..df81f7da 100644 --- a/docs/settings/auth.md +++ b/docs/settings/auth.md @@ -30,7 +30,7 @@ Example: ## AUTH__OIDC_CLIENT_SECRET -When using oauth2/oidc authentication, this variables is oauth2/oidc client secret. +When using OAuth 2.0/OIDC authentication, this variables is client secret. Example: @@ -38,25 +38,21 @@ Example: ## AUTH__OIDC_CLIENT_ID -When using oauth2/oidc authentication, this variables is oauth2/oidc client ID. +When using OAuth 2.0/OIDC authentication, this variables is client ID. Example: - PAPERMERGE__AUTH__OIDC_CLIENT_ID=900000999991-1tmegfjqqqqqqqqqqqqqqqqqqqqv.apps.googleusercontent.com + PAPERMERGE__AUTH__OIDC_CLIENT_ID=papermerge ## AUTH__OIDC_AUTHORIZE_URL -Must be set to fixed value OIDC authorization endpoint: +Must be set to value OIDC authorization endpoint. - PAPERMERGE__AUTH__GOOGLE_AUTHORIZE_URL=http://authk.trusel.net/application/o/authorize/ + PAPERMERGE__AUTH__OIDC_AUTHORIZE_URL=http://keycloak.trusel.net:8080/realms/myrealm/protocol/openid-connect/auth + PAPERMERGE__AUTH__OIDC_AUTHORIZE_URL=http://authk.trusel.net/application/o/authorize/ -## AUTH__OIDC_REDIRECT_URL - -This value always should be set to `:///oidc/callback`. -Example: - - PAPERMERGE__AUTH__OIDC_REDIRECT_URL=http://papermerge.instance.net/oidc/callback +Value points to identity provider's domain. ## AUTH__OIDC_ACCESS_TOKEN_URL @@ -66,6 +62,9 @@ When using oauth2/oidc authentication, this variable contains access token endpo Example: PAPERMERGE__AUTH__OIDC_ACCESS_TOKEN_URL=http://authk.trusel.net/application/o/token/ + PAPERMERGE__AUTH__OIDC_ACCESS_TOKEN_URL=http://keycloak.trusel.net:8080/realms/myrealm/protocol/openid-connect/token + +Value points to identity provider's domain. ## AUTH__OIDC_USER_INFO_URL @@ -75,6 +74,9 @@ When using oauth2/oidc authentication, this variable contains user info endpoint Example: PAPERMERGE__AUTH__OIDC_USER_INFO_URL=http://authk.trusel.net/application/o/userinfo/ + PAPERMERGE__AUTH__OIDC_USER_INFO_URL=http://keycloak.trusel.net:8080/realms/myrealm/protocol/openid-connect/userinfo + +Value points to identity provider's domain. ## AUTH__OIDC_LOGOUT_URL @@ -83,8 +85,35 @@ When using oauth2/oidc authentication, this variable contains logout endpoint. Example: + PAPERMERGE__AUTH__OIDC_LOGOUT_URL=http://keycloak.trusel.net:8080/realms/myrealm/protocol/openid-connect/logout PAPERMERGE__AUTH__OIDC_LOGOUT_URL=http://authk.trusel.net/application/o/calypso/end-session/ +Value points to identity provider's domain. + + +## AUTH__OIDC_INTROSPECT_URL + +Introspect endpoint. + + PAPERMERGE__AUTH__OIDC_INTROSPECT_URL=http://keycloak.trusel.net:8080/realms/myrealm/protocol/openid-connect/token/introspect + +Value points to identity provider domain. +Introspect endpoint is used to validate all incoming requests if they are authenticated or not. + + +## AUTH__OIDC_REDIRECT_URL + +This value always should be set to `:///oidc/callback`. +Example: + + PAPERMERGE__AUTH__OIDC_REDIRECT_URL=http://papermerge.instance.net/oidc/callback + + +Value points to {{ extra.project }} domain. This is basically domain where the identity +provider (e.g. keycloak) will send information, or redirect, after successfull authentication. + + + ## AUTH__LDAP_URL diff --git a/docs/setup/authentication.md b/docs/setup/authentification.md similarity index 53% rename from docs/setup/authentication.md rename to docs/setup/authentification.md index c1d176cd..e4e6a1a3 100644 --- a/docs/setup/authentication.md +++ b/docs/setup/authentification.md @@ -1,29 +1,40 @@ # Authentication -{{ extra.project }} supports different authentication modes: +This section briefly describes authentication. It focuses mostly on relevant +settings so that you can quickly set it up. -- Database (default) -- OAuth 2.0 +For detailed information about each method as well +as how to configure it with specific itenditity provider +see e.g. [oidc](../authentication/oidc/overview.md) + +{{ extra.project }} comes with flexible authentication features. +It can handle user accounts, groups, permissions. {{ extra.project }} +supports following authentication modes: + +- Built-in +- OpenID Connect (OIDC) +- Remote User - LDAP -The difference consist in who decides if user with given username and -password, are valid. In first mode i.e. database authentication - it is - {{ extra.project }} who decides if credentials are valid, this is why this -database authentication is also called "internal authentication". For other -two authentication modes it is the other party, external one like LDAP, which -decides on validity of the given credentials. +## Built-in + +By default {{ extra.project }} will use built-in authentication mechanism. +You only need to provide `PAPERMERGE__AUTH__USERNAME`, `PAPERMERGE__AUTH__PASSWORD` +environment variables. +In this mode there is no external party involved as all +authentication is performed by {{ extra.project }} internal components. -## Database Authentication +Built-in authentication mechanism is suitable for simple setups when +{{ extra.project }} is your only application of concern. -In this case authentication is performed against credentials stored in -database configured with `PAPERMERGE__DATABASE__URL`. This is default -operation mode. +When you want to give users access to multiple applications using same accounts, +you may consider: -## OAuth 2.0/OIDC +## OpenID Connect (OIDC) -{{ extra.project }} supports OAuth 2.0/OpenID Connect authentication protocol. +{{ extra.project }} supports OpenID Connect (OIDC) authentication. In order to enable OIDC authentication you need to provide following environment variables: @@ -33,6 +44,7 @@ In order to enable OIDC authentication you need to provide following environment - PAPERMERGE__AUTH__OIDC_ACCESS_TOKEN_URL - PAPERMERGE__AUTH__OIDC_USER_INFO_URL - PAPERMERGE__AUTH__OIDC_LOGOUT_URL +- PAPERMERGE__AUTH__OIDC_INTROSPECT_URL - PAPERMERGE__AUTH__OIDC_REDIRECT_URL Note that last one, redirect URL, is the only URL pointing to {{ extra.project }} instance domain. @@ -41,13 +53,20 @@ Example: PAPERMERGE__AUTH__OIDC_REDIRECT_URL=https://papermerge.net/oidc/callback -All other URLs, authorize URL, access point URL, user info URL and logout URL, should point -to OIDC provider domain, examples: +All other URLs, authorize URL, access point URL, user info URL, introspect URL and logout URL, +should point to OIDC provider domain. Examples: + + PAPERMERGE__AUTH__OIDC_CLIENT_SECRET=pT5Ff-your-token-eWOSvEPmtyY + PAPERMERGE__AUTH__OIDC_CLIENT_ID=papermerge + PAPERMERGE__AUTH__OIDC_AUTHORIZE_URL=http://keycloak.trusel.net:8080/realms/myrealm/protocol/openid-connect/auth + PAPERMERGE__AUTH__OIDC_ACCESS_TOKEN_URL=http://keycloak.trusel.net:8080/realms/myrealm/protocol/openid-connect/token + PAPERMERGE__AUTH__OIDC_USER_INFO_URL=http://keycloak.trusel.net:8080/realms/myrealm/protocol/openid-connect/userinfo + PAPERMERGE__AUTH__OIDC_LOGOUT_URL=http://keycloak.trusel.net:8080/realms/myrealm/protocol/openid-connect/logout + PAPERMERGE__AUTH__OIDC_INTROSPECT_URL=http://keycloak.trusel.net:8080/realms/myrealm/protocol/openid-connect/token/introspect + +For detailed information OpenID Connect see [Authentication/OIDC](../authentication/oidc/overview.md). - - PAPERMERGE__AUTH__OIDC_AUTHORIZE_URL=http://authk.trusel.net/application/o/authorize/ - - PAPERMERGE__AUTH__OIDC_ACCESS_TOKEN_URL=http://authk.trusel.net/application/o/token/ - - PAPERMERGE__AUTH__OIDC_USER_INFO_URL=http://authk.trusel.net/application/o/userinfo/ - - PAPERMERGE__AUTH__OIDC_LOGOUT_URL=http://authk.trusel.net/application/o/calypso/end-session/ +## Remote User ## LDAP diff --git a/mkdocs.yml b/mkdocs.yml index 774f0476..956e530d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -8,8 +8,8 @@ nav: - setup/docker-compose.md - setup/ansible.md - setup/kubernetes.md + - setup/authentification.md - setup/add-ocr-langs.md - - setup/authentication.md - User Manual: - user/getting-started.md - user/user-interface.md @@ -22,6 +22,10 @@ nav: - user/ocr.md - user/file-formats.md - user/user-management.md + - Authentication: + - OIDC: + - authentication/oidc/overview.md + - authentication/oidc/keycloak.md - Settings: - settings/overview.md - settings/auth.md