feat(oidc): private_key_jwt client auth (#5280)

This adds support for the private_key_jwt client authentication method.

Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
Co-authored-by: Amir Zarrinkafsh <nightah@me.com>
pull/5437/head
James Elliott 2023-05-15 10:32:10 +10:00 committed by GitHub
parent cef374cdc1
commit 65ecfe4b9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
83 changed files with 5201 additions and 2934 deletions

View File

@ -4,7 +4,16 @@
# Authelia Configuration #
###############################################################################
## Note: the container by default expects to find this file at /config/configuration.yml.
##
## Notes:
##
## - the default location of this file is assumed to be configuration.yml unless otherwise noted
## - when using docker the container expects this by default to be at /config/configuration.yml
## - the default location where this file is loaded from can be overridden with the X_AUTHELIA_CONFIG environment var
## - the comments in this configuration file are helpful but users should consult the official documentation on the
## website at https://www.authelia.com/ or https://www.authelia.com/configuration/prologue/introduction/
## - this configuration file template is not automatically updated
##
## Certificates directory specifies where Authelia will load trusted certificates (public portion) from in addition to
## the system certificates store.
@ -357,73 +366,37 @@ authentication_backend:
## i.e. Mutual TLS.
# certificate_chain: |
# -----BEGIN CERTIFICATE-----
# MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
# EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
# MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
# ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q
# /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6
# LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY
# 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H
# kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR
# Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
# AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
# AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh
# /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4
# lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq
# wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg
# OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i
# ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE=
# MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT
# MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3
# NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
# AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00
# z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8
# Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx
# 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE=
# -----END CERTIFICATE-----
# -----BEGIN CERTIFICATE-----
# MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw
# EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
# MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
# ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S
# zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50
# 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou
# kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7
# ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi
# Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD
# AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
# Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/
# kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf
# 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ
# HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB
# D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj
# 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b
# qocikt3WAdU^invalid DO NOT USE=
# MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT
# MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3
# NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
# AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00
# z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8
# Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx
# 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE=
# -----END CERTIFICATE-----
## The private key used with the certificate_chain if the server requests TLS Client Authentication
## i.e. Mutual TLS.
# private_key: |
# -----BEGIN RSA PRIVATE KEY-----
# MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1
# T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ
# KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+
# +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW
# LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5
# txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um
# aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc
# Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB
# ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr
# LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG
# jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5
# BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR
# Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV
# R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i
# tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9
# ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR
# lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx
# 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos
# fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ
# 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX
# jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe
# rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84
# n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk
# yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ
# 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid..
# DO NOT USE==
# -----BEGIN RSA PRIVATE KEY-----
# MIIBPAIBAAJBAK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZF
# p7aTcToHMf00z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAQJBAJdpB0+RQ9ZFwy9Uk38P
# 5zZpUB8cL8ZFeEFluQeVbt0vyNa+cPLvDLouY87onduXtMz5AKIatLaTOjuG2thh
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
# -----END RSA PRIVATE KEY-----
## The distinguished name of the container searched for objects in the directory information tree.
@ -485,7 +458,7 @@ authentication_backend:
# permit_referrals: false
## The username and password of the admin user.
# user: cn=admin,dc=example,dc=com
# user: 'cn=admin,dc=example,dc=com'
## Password can also be set using a secret: https://www.authelia.com/c/secrets
# password: 'password'
@ -622,7 +595,7 @@ access_control:
# networks:
# - '10.10.0.0/16'
# - '192.168.2.0/24'
# - name: VPN
# - name: 'VPN'
# networks: '10.9.0.0/16'
# rules:
@ -748,7 +721,8 @@ session:
# expiration: '1h'
## The time before the cookie expires and the session is destroyed if remember me IS selected by the user. Setting
## this value to -1 disables remember me for this session cookie domain.
## this value to -1 disables remember me for this session cookie domain. If allowed and the user uses the remember
## me checkbox this overrides the expiration option and disables the inactivity option.
# remember_me: '1M'
## Cookie Session Domain default 'name' value.
@ -816,73 +790,37 @@ session:
## i.e. Mutual TLS.
# certificate_chain: |
# -----BEGIN CERTIFICATE-----
# MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
# EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
# MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
# ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q
# /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6
# LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY
# 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H
# kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR
# Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
# AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
# AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh
# /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4
# lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq
# wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg
# OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i
# ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE=
# MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT
# MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3
# NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
# AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00
# z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8
# Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx
# 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE=
# -----END CERTIFICATE-----
# -----BEGIN CERTIFICATE-----
# MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw
# EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
# MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
# ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S
# zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50
# 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou
# kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7
# ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi
# Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD
# AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
# Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/
# kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf
# 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ
# HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB
# D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj
# 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b
# qocikt3WAdU^invalid DO NOT USE=
# MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT
# MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3
# NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
# AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00
# z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8
# Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx
# 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE=
# -----END CERTIFICATE-----
## The private key used with the certificate_chain if the server requests TLS Client Authentication
## i.e. Mutual TLS.
# private_key: |
# -----BEGIN RSA PRIVATE KEY-----
# MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1
# T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ
# KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+
# +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW
# LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5
# txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um
# aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc
# Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB
# ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr
# LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG
# jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5
# BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR
# Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV
# R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i
# tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9
# ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR
# lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx
# 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos
# fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ
# 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX
# jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe
# rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84
# n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk
# yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ
# 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid..
# DO NOT USE==
# -----BEGIN RSA PRIVATE KEY-----
# MIIBPAIBAAJBAK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZF
# p7aTcToHMf00z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAQJBAJdpB0+RQ9ZFwy9Uk38P
# 5zZpUB8cL8ZFeEFluQeVbt0vyNa+cPLvDLouY87onduXtMz5AKIatLaTOjuG2thh
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
# -----END RSA PRIVATE KEY-----
## The Redis HA configuration options.
@ -997,73 +935,37 @@ regulation:
## i.e. Mutual TLS.
# certificate_chain: |
# -----BEGIN CERTIFICATE-----
# MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
# EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
# MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
# ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q
# /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6
# LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY
# 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H
# kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR
# Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
# AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
# AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh
# /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4
# lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq
# wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg
# OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i
# ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE=
# MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT
# MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3
# NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
# AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00
# z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8
# Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx
# 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE=
# -----END CERTIFICATE-----
# -----BEGIN CERTIFICATE-----
# MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw
# EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
# MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
# ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S
# zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50
# 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou
# kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7
# ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi
# Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD
# AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
# Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/
# kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf
# 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ
# HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB
# D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj
# 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b
# qocikt3WAdU^invalid DO NOT USE=
# MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT
# MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3
# NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
# AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00
# z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8
# Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx
# 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE=
# -----END CERTIFICATE-----
## The private key used with the certificate_chain if the server requests TLS Client Authentication
## i.e. Mutual TLS.
# private_key: |
# -----BEGIN RSA PRIVATE KEY-----
# MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1
# T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ
# KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+
# +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW
# LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5
# txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um
# aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc
# Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB
# ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr
# LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG
# jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5
# BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR
# Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV
# R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i
# tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9
# ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR
# lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx
# 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos
# fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ
# 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX
# jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe
# rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84
# n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk
# yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ
# 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid..
# DO NOT USE==
# -----BEGIN RSA PRIVATE KEY-----
# MIIBPAIBAAJBAK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZF
# p7aTcToHMf00z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAQJBAJdpB0+RQ9ZFwy9Uk38P
# 5zZpUB8cL8ZFeEFluQeVbt0vyNa+cPLvDLouY87onduXtMz5AKIatLaTOjuG2thh
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
# -----END RSA PRIVATE KEY-----
##
@ -1116,74 +1018,38 @@ regulation:
## i.e. Mutual TLS.
# certificate_chain: |
# -----BEGIN CERTIFICATE-----
# MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
# EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
# MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
# ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q
# /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6
# LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY
# 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H
# kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR
# Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
# AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
# AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh
# /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4
# lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq
# wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg
# OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i
# ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE=
# MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT
# MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3
# NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
# AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00
# z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8
# Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx
# 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE=
# -----END CERTIFICATE-----
# -----BEGIN CERTIFICATE-----
# MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw
# EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
# MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
# ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S
# zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50
# 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou
# kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7
# ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi
# Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD
# AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
# Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/
# kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf
# 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ
# HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB
# D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj
# 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b
# qocikt3WAdU^invalid DO NOT USE=
# MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT
# MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3
# NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
# AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00
# z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8
# Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx
# 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE=
# -----END CERTIFICATE-----
## The private key used with the certificate_chain if the server requests TLS Client Authentication
## i.e. Mutual TLS.
# private_key: |
# -----BEGIN RSA PRIVATE KEY-----
# MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1
# T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ
# KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+
# +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW
# LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5
# txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um
# aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc
# Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB
# ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr
# LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG
# jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5
# BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR
# Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV
# R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i
# tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9
# ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR
# lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx
# 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos
# fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ
# 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX
# jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe
# rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84
# n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk
# yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ
# 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid..
# DO NOT USE==
# -----END RSA PRIVATE KEY-----
# -----BEGIN RSA PRIVATE KEY-----
# MIIBPAIBAAJBAK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZF
# p7aTcToHMf00z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAQJBAJdpB0+RQ9ZFwy9Uk38P
# 5zZpUB8cL8ZFeEFluQeVbt0vyNa+cPLvDLouY87onduXtMz5AKIatLaTOjuG2thh
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
# -----END RSA PRIVATE KEY-----
##
## Notification Provider
@ -1270,73 +1136,37 @@ notifier:
## i.e. Mutual TLS.
# certificate_chain: |
# -----BEGIN CERTIFICATE-----
# MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
# EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
# MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
# ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q
# /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6
# LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY
# 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H
# kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR
# Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
# AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
# AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh
# /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4
# lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq
# wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg
# OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i
# ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE=
# MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT
# MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3
# NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
# AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00
# z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8
# Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx
# 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE=
# -----END CERTIFICATE-----
# -----BEGIN CERTIFICATE-----
# MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw
# EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
# MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
# ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S
# zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50
# 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou
# kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7
# ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi
# Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD
# AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
# Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/
# kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf
# 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ
# HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB
# D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj
# 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b
# qocikt3WAdU^invalid DO NOT USE=
# MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT
# MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3
# NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
# AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00
# z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8
# Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx
# 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE=
# -----END CERTIFICATE-----
## The private key used with the certificate_chain if the server requests TLS Client Authentication
## i.e. Mutual TLS.
# private_key: |
# -----BEGIN RSA PRIVATE KEY-----
# MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1
# T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ
# KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+
# +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW
# LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5
# txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um
# aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc
# Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB
# ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr
# LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG
# jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5
# BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR
# Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV
# R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i
# tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9
# ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR
# lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx
# 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos
# fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ
# 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX
# jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe
# rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84
# n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk
# yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ
# 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid..
# DO NOT USE==
# -----BEGIN RSA PRIVATE KEY-----
# MIIBPAIBAAJBAK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZF
# p7aTcToHMf00z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAQJBAJdpB0+RQ9ZFwy9Uk38P
# 5zZpUB8cL8ZFeEFluQeVbt0vyNa+cPLvDLouY87onduXtMz5AKIatLaTOjuG2thh
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
# -----END RSA PRIVATE KEY-----
##
@ -1354,80 +1184,88 @@ notifier:
## HMAC Secret can also be set using a secret: https://www.authelia.com/c/secrets
# hmac_secret: 'this_is_a_secret_abc123abc123abc'
## The issuer_certificate_chain is an optional PEM encoded certificate chain. It's used in conjunction with the
## issuer_private_key to sign JWT's. All certificates in the chain must be within the validity period, and every
## certificate included must be signed by the certificate immediately after it if provided.
# issuer_certificate_chain: |
# -----BEGIN CERTIFICATE-----
# MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
# EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
# MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
# ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q
# /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6
# LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY
# 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H
# kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR
# Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
# AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
# AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh
# /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4
# lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq
# wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg
# OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i
# ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE=
# -----END CERTIFICATE-----
# -----BEGIN CERTIFICATE-----
# MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw
# EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
# MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
# ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S
# zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50
# 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou
# kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7
# ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi
# Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD
# AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
# Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/
# kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf
# 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ
# HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB
# D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj
# 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b
# qocikt3WAdU^invalid DO NOT USE=
# -----END CERTIFICATE-----
## Issuer JWKS configures multiple JSON Web Keys. It's required that at least one of these is RS256 or the
## option issuer_private_key is configured. There must only be one key per algorithm at this time.
## For RSA keys the minimum is a 2048 bit key.
# issuer_private_keys:
# -
## Key ID embedded into the JWT header for key matching. Must be an alphanumeric string with 7 or less characters.
## This value is automatically generated if not provided. It's recommended to not configure this.
# key_id: 'example'
## The issuer_private_key is used to sign the JWT forged by OpenID Connect.
## The key algorithm used with this key.
# algorithm: 'RS256'
## The key use expected with this key. Currently only 'sig' is supported.
# use: 'sig'
## Required Private Key in PEM DER form.
# key: |
# -----BEGIN RSA PRIVATE KEY-----
# MIIBPAIBAAJBAK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZF
# p7aTcToHMf00z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAQJBAJdpB0+RQ9ZFwy9Uk38P
# 5zZpUB8cL8ZFeEFluQeVbt0vyNa+cPLvDLouY87onduXtMz5AKIatLaTOjuG2thh
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
# -----END RSA PRIVATE KEY-----
## Optional matching certificate chain in PEM DER form that matches the key. All certificates within the chain
## must be valid and current, and from top to bottom each certificate must be signed by the subsequent one.
# certificate_chain: |
# -----BEGIN CERTIFICATE-----
# MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT
# MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3
# NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
# AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00
# z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8
# Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx
# 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE=
# -----END CERTIFICATE-----
## The issuer_private_key is used to sign the JWT forged by OpenID Connect. This is in addition to the
## issuer_private_keys option. Assumed to use the RS256 algorithm, and must not be specified if any of the
## keys in issuer_private_keys also has the algorithm RS256 or are an RSA key without an algorithm.
## Issuer Private Key can also be set using a secret: https://www.authelia.com/c/secrets
# issuer_private_key: |
# -----BEGIN RSA PRIVATE KEY-----
# MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1
# T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ
# KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+
# +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW
# LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5
# txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um
# aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc
# Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB
# ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr
# LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG
# jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5
# BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR
# Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV
# R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i
# tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9
# ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR
# lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx
# 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos
# fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ
# 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX
# jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe
# rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84
# n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk
# yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ
# 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid..
# DO NOT USE==
# MIIBPAIBAAJBAK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZF
# p7aTcToHMf00z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAQJBAJdpB0+RQ9ZFwy9Uk38P
# 5zZpUB8cL8ZFeEFluQeVbt0vyNa+cPLvDLouY87onduXtMz5AKIatLaTOjuG2thh
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
# -----END RSA PRIVATE KEY-----
## Optional matching certificate chain in PEM DER form that matches the issuer_private_key. All certificates within
## the chain must be valid and current, and from top to bottom each certificate must be signed by the next
## certificate in the chain if provided.
# issuer_certificate_chain: |
# -----BEGIN CERTIFICATE-----
# MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT
# MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3
# NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
# AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00
# z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8
# Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx
# 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE=
# -----END CERTIFICATE-----
# -----BEGIN CERTIFICATE-----
# MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT
# MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3
# NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
# AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00
# z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8
# Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx
# 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE=
# -----END CERTIFICATE-----
## The lifespans configure the expiration for these token types in the duration common syntax.
# access_token_lifespan: '1h'
# authorize_code_lifespan: '1m'
@ -1499,6 +1337,11 @@ notifier:
# - 'email'
# - 'profile'
## Grant Types configures which grants this client can obtain.
## It's not recommended to define this unless you know what you're doing.
# grant_types:
# - 'authorization_code'
## Response Types configures which responses this client can be sent.
## It's not recommended to define this unless you know what you're doing.
# response_types:
@ -1509,25 +1352,19 @@ notifier:
# - 'form_post'
# - 'query'
## Grant Types configures which grants this client can obtain.
## It's not recommended to define this unless you know what you're doing.
# grant_types:
# - 'authorization_code'
## The permitted client authentication method for the Token Endpoint for this client.
# token_endpoint_auth_method: 'client_secret_basic'
## The permitted client authentication signing algorithm for the Token Endpoint for this client when using
## the 'client_secret_jwt' token_endpoint_auth_method.
# token_endpoint_auth_signing_alg: HS256
## The permitted client authentication signing algorithm for the Token Endpoint for this client when using
## the 'client_secret_jwt' token_endpoint_auth_method.
# token_endpoint_auth_signing_alg: HS256
## The policy to require for this client; one_factor or two_factor.
# authorization_policy: 'two_factor'
## The consent mode controls how consent is obtained.
# consent_mode: 'auto'
## This value controls the duration a consent on this client remains remembered when the consent mode is
## configured as 'auto' or 'pre-configured' in the duration common syntax.
# pre_configured_consent_duration: '1w'
## Enforces the use of Pushed Authorization Requests for this client when set to true.
# enforce_par: false
## Enforces the use of PKCE for this client when set to true.
# enforce_pkce: false
@ -1535,13 +1372,69 @@ notifier:
## Options are 'plain' and 'S256'.
# pkce_challenge_method: 'S256'
## The permitted client authentication method for the Token Endpoint for this client.
# token_endpoint_auth_method: 'client_secret_basic'
## The permitted client authentication signing algorithm for the Token Endpoint for this client when using
## the 'client_secret_jwt' or 'private_key_jwt' token_endpoint_auth_method.
# token_endpoint_auth_signing_alg: 'RS256'
## The signing algorithm which must be used for request objects. A client JWK with a matching algorithm must be
## included if configured.
# request_object_signing_alg: 'RS256'
## The signing algorithm used for ID Tokens. Am issuer JWK with a matching algorithm must be included.
# id_token_signing_alg: 'RS256'
## The algorithm used to sign userinfo endpoint responses for this client, either none or RS256.
# userinfo_signing_algorithm: 'none'
# userinfo_signing_alg: 'none'
## The consent mode controls how consent is obtained.
# consent_mode: 'auto'
## Trusted public keys configuration for request object signing for things such as private_key_jwt
# public_keys:
## This value controls the duration a consent on this client remains remembered when the consent mode is
## configured as 'auto' or 'pre-configured' in the duration common syntax.
# pre_configured_consent_duration: '1w'
## URL of the HTTPS endpoint which serves the keys. It's recommended to manually configure them in the
## values option below. Please note the URL and the individual values are mutually exclusive.
# uri: 'https://app.example.com/jwks.json'
## Values from the individual keys.
# values:
# -
## Key ID used to match the JWT's to an individual identifier. This option is required if configured.
# key_id: 'example'
## The key algorithm expected with this key.
# algorithm: 'RS256'
## The key use expected with this key. Currently only 'sig' is supported.
# use: 'sig'
## Required Public Key in PEM DER form.
# key: |
# -----BEGIN RSA PUBLIC KEY-----
# MEgCQQDAwV26ZA1lodtOQxNrJ491gWT+VzFum9IeZ+WTmMypYWyW1CzXKwsvTHDz
# 9ec+jserR3EMQ0Rr24lj13FL1ib5AgMBAAE=
# -----END RSA PUBLIC KEY----
## The matching certificate chain in PEM DER form that matches the key if available.
# certificate_chain: |
# -----BEGIN CERTIFICATE-----
# MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT
# MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3
# NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
# AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00
# z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8
# Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx
# 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE=
# -----END CERTIFICATE-----
# -----BEGIN CERTIFICATE-----
# MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT
# MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3
# NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
# AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00
# z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8
# Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx
# 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE=
# -----END CERTIFICATE-----
...

View File

@ -16,4 +16,4 @@ aliases:
## OpenID Connect
The only identity provider implementation supported at this time is [OpenID Connect 1.0](open-id-connect.md).
The only identity provider implementation supported at this time is [OpenID Connect 1.0](openid-connect/provider.md).

View File

@ -1,671 +0,0 @@
---
title: "OpenID Connect"
description: "OpenID Connect Configuration"
lead: "Authelia can operate as an OpenID Connect 1.0 Provider. This section describes how to configure this."
date: 2022-06-15T17:51:47+10:00
draft: false
images: []
menu:
configuration:
parent: "identity-providers"
weight: 190200
toc: true
aliases:
- /c/oidc
- /docs/configuration/identity-providers/oidc.html
---
__Authelia__ currently supports the [OpenID Connect 1.0] Provider role as an open
[__beta__](../../roadmap/active/openid-connect.md) feature. We currently do not support the [OpenID Connect 1.0] Relying
Party role. This means other applications that implement the [OpenID Connect 1.0] Relying Party role can use Authelia as
an [OpenID Connect 1.0] Provider similar to how you may use social media or development platforms for login.
The [OpenID Connect 1.0] Relying Party role is the role which allows an application to use GitHub, Google, or other
[OpenID Connect 1.0] Providers for authentication and authorization. We do not intend to support this functionality at
this moment in time.
More information about the beta can be found in the [roadmap](../../roadmap/active/openid-connect.md).
## Configuration
The following snippet provides a sample-configuration for the OIDC identity provider explaining each field in detail.
```yaml
identity_providers:
oidc:
hmac_secret: this_is_a_secret_abc123abc123abc
issuer_certificate_chain: |
-----BEGIN CERTIFICATE-----
MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q
/Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6
LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY
91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H
kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR
Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh
/ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4
lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq
wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg
OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i
ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw
EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S
zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50
5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou
kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7
ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi
Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD
AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/
kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf
71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ
HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB
D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj
2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b
qocikt3WAdU^invalid DO NOT USE=
-----END CERTIFICATE-----
issuer_private_key: |
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1
T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ
KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+
+5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW
LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5
txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um
aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc
Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB
ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr
LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG
jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5
BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR
Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV
R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i
tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9
ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR
lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx
6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos
fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ
9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX
jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe
rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84
n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk
yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ
27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid..
DO NOT USE==
-----END RSA PRIVATE KEY-----
issuer_jwks:
- key_id: ''
algorithm: 'RS256'
key: |
<private key data>
certificate_chain: |
<certificate chain data>
access_token_lifespan: 1h
authorize_code_lifespan: 1m
id_token_lifespan: 1h
refresh_token_lifespan: 90m
enable_client_debug_messages: false
enforce_pkce: public_clients_only
cors:
endpoints:
- authorization
- token
- revocation
- introspection
allowed_origins:
- https://example.com
allowed_origins_from_client_redirect_uris: false
clients:
- id: myapp
description: My Application
secret: '$pbkdf2-sha512$310000$c8p78n7pUMln0jzvd4aK4Q$JNRBzwAo0ek5qKn50cFzzvE9RXV88h1wJn5KGiHrD0YKtZaR/nCb2CJPOsKaPK0hjf.9yHxzQGZziziccp6Yng' # The digest of 'insecure_secret'.
sector_identifier: ''
public: false
authorization_policy: two_factor
consent_mode: explicit
pre_configured_consent_duration: 1w
audience: []
scopes:
- openid
- groups
- email
- profile
redirect_uris:
- https://oidc.example.com:8080/oauth2/callback
grant_types:
- refresh_token
- authorization_code
response_types:
- code
response_modes:
- form_post
- query
- fragment
userinfo_signing_algorithm: none
```
## Options
### hmac_secret
{{< confkey type="string" required="yes" >}}
*__Important Note:__ This can also be defined using a [secret](../methods/secrets.md) which is __strongly recommended__
especially for containerized deployments.*
The HMAC secret used to sign the [JWT]'s. The provided string is hashed to a SHA256 ([RFC6234]) byte string for the
purpose of meeting the required format.
It's __strongly recommended__ this is a
[Random Alphanumeric String](../../reference/guides/generating-secure-values.md#generating-a-random-alphanumeric-string)
with 64 or more characters.
### issuer_private_key
{{< confkey type="string" required="yes" >}}
*__Important Note:__ This can also be defined using a [secret](../methods/secrets.md) which is __strongly recommended__
especially for containerized deployments.*
The private key used to sign/encrypt the [OpenID Connect 1.0] issued [JWT]'s. The key must be generated by the administrator
and can be done by following the
[Generating an RSA Keypair](../../reference/guides/generating-secure-values.md#generating-an-rsa-keypair) guide.
The private key *__MUST__*:
* Be a PEM block encoded in the DER base64 format ([RFC4648]).
* Be an RSA Key.
* Have a key size of at least 2048 bits.
If the [issuer_certificate_chain](#issuercertificatechain) is provided the private key must include matching public
key data for the first certificate in the chain.
### issuer_certificate_chain
{{< confkey type="string" required="no" >}}
The certificate chain/bundle to be used with the [issuer_private_key](#issuer_private_key) DER base64 ([RFC4648])
encoded PEM format used to sign/encrypt the [OpenID Connect 1.0] [JWT]'s. When configured it enables the [x5c] and [x5t]
JSON key's in the JWKs [Discoverable Endpoint](../../integration/openid-connect/introduction.md#discoverable-endpoints)
as per [RFC7517].
[RFC7517]: https://datatracker.ietf.org/doc/html/rfc7517
[x5c]: https://datatracker.ietf.org/doc/html/rfc7517#section-4.7
[x5t]: https://datatracker.ietf.org/doc/html/rfc7517#section-4.8
The first certificate in the chain must have the public key for the [issuer_private_key](#issuerprivatekey), each
certificate in the chain must be valid for the current date, and each certificate in the chain should be signed by the
certificate immediately following it if present.
### issuer_jwks
{{< confkey type="list(object" required="no" >}}
The list of JWKS instead of or in addition to the [issuer_private_key](#issuerprivatekey) and
[issuer_certificate_chain](#issuercertificatechain). Can also accept ECDSA Private Key's and Certificates.
#### key_id
{{< confkey type="string" default="<thumbprint of public key>" required="no" >}}
Completely optional, and generally discouraged unless there is a collision between the automatically generated key id's.
If provided must be a unique string with 7 or less alphanumeric characters.
This value is the first 7 characters of the public key thumbprint (SHA1) encoded into hexadecimal.
#### algorithm
{{< confkey type="string" required="no" >}}
The algorithm for this key. This value must be unique. It's automatically detected based on the type of key.
#### key
{{< confkey type="string" required="yes" >}}
The private key associated with this key entry.
The private key used to sign/encrypt the [OpenID Connect 1.0] issued [JWT]'s. The key must be generated by the administrator
and can be done by following the
[Generating an RSA Keypair](../../reference/guides/generating-secure-values.md#generating-an-rsa-keypair) guide.
The private key *__MUST__*:
* Be a PEM block encoded in the DER base64 format ([RFC4648]).
* Be one of:
* An RSA key with a key size of at least 2048 bits.
* An ECDSA private key with one of the P-256, P-384, or P-521 elliptical curves.
If the [certificate_chain](#certificatechain) is provided the private key must include matching public
key data for the first certificate in the chain.
#### certificate_chain
{{< confkey type="string" required="no" >}}
The certificate chain/bundle to be used with the [key](#key) DER base64 ([RFC4648])
encoded PEM format used to sign/encrypt the [OpenID Connect 1.0] [JWT]'s. When configured it enables the [x5c] and [x5t]
JSON key's in the JWKs [Discoverable Endpoint](../../integration/openid-connect/introduction.md#discoverable-endpoints)
as per [RFC7517].
[RFC7517]: https://datatracker.ietf.org/doc/html/rfc7517
[x5c]: https://datatracker.ietf.org/doc/html/rfc7517#section-4.7
[x5t]: https://datatracker.ietf.org/doc/html/rfc7517#section-4.8
The first certificate in the chain must have the public key for the [key](#key), each certificate in the chain must be
valid for the current date, and each certificate in the chain should be signed by the certificate immediately following
it if present.
### access_token_lifespan
{{< confkey type="duration" default="1h" required="no" >}}
The maximum lifetime of an access token. It's generally recommended keeping this short similar to the default.
For more information read these docs about [token lifespan].
### authorize_code_lifespan
{{< confkey type="duration" default="1m" required="no" >}}
The maximum lifetime of an authorize code. This can be rather short, as the authorize code should only be needed to
obtain the other token types. For more information read these docs about [token lifespan].
### id_token_lifespan
{{< confkey type="duration" default="1h" required="no" >}}
The maximum lifetime of an ID token. For more information read these docs about [token lifespan].
### refresh_token_lifespan
{{< confkey type="string" default="90m" required="no" >}}
The maximum lifetime of a refresh token. The
refresh token can be used to obtain new refresh tokens as well as access tokens or id tokens with an
up-to-date expiration. For more information read these docs about [token lifespan].
A good starting point is 50% more or 30 minutes more (which ever is less) time than the highest lifespan out of the
[access token lifespan](#access_token_lifespan), the [authorize code lifespan](#authorize_code_lifespan), and the
[id token lifespan](#id_token_lifespan). For instance the default for all of these is 60 minutes, so the default refresh
token lifespan is 90 minutes.
### enable_client_debug_messages
{{< confkey type="boolean" default="false" required="no" >}}
Allows additional debug messages to be sent to the clients.
### minimum_parameter_entropy
{{< confkey type="integer" default="8" required="no" >}}
This controls the minimum length of the `nonce` and `state` parameters.
*__Security Notice:__* Changing this value is generally discouraged, reducing it from the default can theoretically
make certain scenarios less secure. It is highly encouraged that if your OpenID Connect RP does not send these
parameters or sends parameters with a lower length than the default that they implement a change rather than changing
this value.
### enforce_pkce
{{< confkey type="string" default="public_clients_only" required="no" >}}
[Proof Key for Code Exchange](https://datatracker.ietf.org/doc/html/rfc7636) enforcement policy: if specified, must be
either `never`, `public_clients_only` or `always`.
If set to `public_clients_only` (default), [PKCE] will be required for public clients using the
[Authorization Code Flow].
When set to `always`, [PKCE] will be required for all clients using the Authorization Code flow.
*__Security Notice:__* Changing this value to `never` is generally discouraged, reducing it from the default can
theoretically make certain client-side applications (mobile applications, SPA) vulnerable to CSRF and authorization code
interception attacks.
### enable_pkce_plain_challenge
{{< confkey type="boolean" default="false" required="no" >}}
Allows [PKCE] `plain` challenges when set to `true`.
*__Security Notice:__* Changing this value is generally discouraged. Applications should use the `S256` [PKCE] challenge
method instead.
### pushed_authorizations
Controls the behaviour of [Pushed Authorization Requests].
#### enforce
{{< confkey type="boolean" default="false" required="no" >}}
When enabled all authorization requests must use the [Pushed Authorization Requests] flow.
#### context_lifespan
{{< confkey type="duration" default="5m" required="no" >}}
The maximum amount of time between the [Pushed Authorization Requests] flow being initiated and the generated
`request_uri` being utilized by a client.
### cors
Some [OpenID Connect 1.0] Endpoints need to allow cross-origin resource sharing, however some are optional. This section allows
you to configure the optional parts. We reply with CORS headers when the request includes the Origin header.
#### endpoints
{{< confkey type="list(string)" required="no" >}}
A list of endpoints to configure with cross-origin resource sharing headers. It is recommended that the `userinfo`
option is at least in this list. The potential endpoints which this can be enabled on are as follows:
* authorization
* pushed-authorization-request
* token
* revocation
* introspection
* userinfo
#### allowed_origins
{{< confkey type="list(string)" required="no" >}}
A list of permitted origins.
Any origin with https is permitted unless this option is configured or the
[allowed_origins_from_client_redirect_uris](#allowed_origins_from_client_redirect_uris) option is enabled. This means
you must configure this option manually if you want http endpoints to be permitted to make cross-origin requests to the
[OpenID Connect 1.0] endpoints, however this is not recommended.
Origins must only have the scheme, hostname and port, they may not have a trailing slash or path.
In addition to an Origin URI, you may specify the wildcard origin in the allowed_origins. It MUST be specified by itself
and the [allowed_origins_from_client_redirect_uris](#allowedoriginsfromclientredirecturis) MUST NOT be enabled. The
wildcard origin is denoted as `*`. Examples:
```yaml
identity_providers:
oidc:
cors:
allowed_origins: "*"
```
```yaml
identity_providers:
oidc:
cors:
allowed_origins:
- "*"
```
#### allowed_origins_from_client_redirect_uris
{{< confkey type="boolean" default="false" required="no" >}}
Automatically adds the origin portion of all redirect URI's on all clients to the list of
[allowed_origins](#allowed_origins), provided they have the scheme http or https and do not have the hostname of
localhost.
### clients
{{< confkey type="list" required="yes" >}}
A list of clients to configure. The options for each client are described below.
#### id
{{< confkey type="string" required="yes" >}}
The Client ID for this client. It must exactly match the Client ID configured in the application
consuming this client.
#### description
{{< confkey type="string" default="*same as id*" required="no" >}}
A friendly description for this client shown in the UI. This defaults to the same as the ID.
#### secret
{{< confkey type="string" required="situational" >}}
The shared secret between Authelia and the application consuming this client. This secret must match the secret
configured in the application.
This secret must be generated by the administrator and can be done by following the
[How Do I Generate Client Secrets](../../integration/openid-connect/frequently-asked-questions.md#how-do-i-generate-client-secrets) FAQ.
This must be provided when the client is a confidential client type, and must be blank when using the public client
type. To set the client type to public see the [public](#public) configuration option.
#### sector_identifier
{{< confkey type="string" required="no" >}}
*__Important Note:__ because adjusting this option will inevitably change the `sub` claim of all tokens generated for
the specified client, changing this should cause the relying party to detect all future authorizations as completely new
users.*
Must be an empty string or the host component of a URL. This is commonly just the domain name, but may also include a
port.
Authelia utilizes UUID version 4 subject identifiers. By default the public [Subject Identifier Type] is utilized for
all clients. This means the subject identifiers will be the same for all clients. This configuration option enables
[Pairwise Identifier Algorithm] for this client, and configures the sector identifier utilized for both the storage and
the lookup of the subject identifier.
1. All clients who do not have this configured will generate the same subject identifier for a particular user
regardless of which client obtains the ID token.
2. All clients which have the same sector identifier will:
1. have the same subject identifier for a particular user when compared to clients with the same sector identifier.
2. have a completely different subject identifier for a particular user whe compared to:
1. any client with the public subject identifier type.
2. any client with a differing sector identifier.
In specific but limited scenarios this option is beneficial for privacy reasons. In particular this is useful when the
party utilizing the *Authelia* [OpenID Connect 1.0] Authorization Server is foreign and not controlled by the user. It would
prevent the third party utilizing the subject identifier with another third party in order to track the user.
Keep in mind depending on the other claims they may still be able to perform this tracking and it is not a silver
bullet. There are very few benefits when utilizing this in a homelab or business where no third party is utilizing
the server.
#### public
{{< confkey type="bool" default="false" required="no" >}}
This enables the public client type for this client. This is for clients that are not capable of maintaining
confidentiality of credentials, you can read more about client types in [RFC6749 Section 2.1]. This is particularly
useful for SPA's and CLI tools. This option requires setting the [client secret](#secret) to a blank string.
#### redirect_uris
{{< confkey type="list(string)" required="yes" >}}
A list of valid callback URIs this client will redirect to. All other callbacks will be considered unsafe. The URIs are
case-sensitive and they differ from application to application - the community has provided
[a list of URL´s for common applications](../../integration/openid-connect/introduction.md).
Some restrictions that have been placed on clients and
their redirect URIs are as follows:
1. If a client attempts to authorize with Authelia and its redirect URI is not listed in the client configuration the
attempt to authorize will fail and an error will be generated.
2. The redirect URIs are case-sensitive.
3. The URI must include a scheme and that scheme must be one of `http` or `https`.
#### audience
{{< confkey type="list(string)" required="no" >}}
A list of audiences this client is allowed to request.
#### scopes
{{< confkey type="list(string)" default="openid, groups, profile, email" required="no" >}}
A list of scopes to allow this client to consume. See
[scope definitions](../../integration/openid-connect/introduction.md#scope-definitions) for more information. The
documentation for the application you are trying to configure [OpenID Connect 1.0] for will likely have a list of scopes
or claims required which can be matched with the above guide.
#### response_types
{{< confkey type="list(string)" default="code" required="no" >}}
*__Security Note:__ It is recommended that only the `code` response type (i.e. the default) is used. The other response
types are not as secure as this response type.*
A list of response types this client supports. If a response type not in this list is requested by a client then an
error will be returned to the client. The response type indicates the types of values that are returned to the client.
See the [Response Types](../../integration/openid-connect/introduction.md#response-types) section of the
[OpenID Connect 1.0 Integration Guide](../../integration/openid-connect/introduction.md#response-types) for more information.
#### response_modes
{{< confkey type="list(string)" default="form_post, query" required="no" >}}
*__Important Note:__ It is recommended that this isn't configured at this time unless you know what you're doing.*
A list of response modes this client supports. If a response mode not in this list is requested by a client then an
error will be returned to the client. The response mode controls how the response type is returned to the client.
See the [Response Modes](../../integration/openid-connect/introduction.md#response-modes) section of the
[OpenID Connect 1.0 Integration Guide](../../integration/openid-connect/introduction.md#response-modes) for more
information.
The default values are based on the [response_types](#responsetypes) values. When the [response_types](#responsetypes)
values include the `code` type then the `query` response mode will be included. When any other type is included the
`fragment` response mode will be included. It's important to note at this time we do not support the `none` response
type, but when it is supported it will include the `query` response mode.
#### grant_types
{{< confkey type="list(string)" default="authorization_code" required="no" >}}
*__Important Note:__ It is recommended that this isn't configured at this time unless you know what you're doing.*
The list of grant types this client is permitted to use in order to obtain access to the relevant tokens.
See the [Grant Types](../../integration/openid-connect/introduction.md#grant-types) section of the
[OpenID Connect 1.0 Integration Guide](../../integration/openid-connect/introduction.md#grant-types) for more information.
#### authorization_policy
{{< confkey type="string" default="two_factor" required="no" >}}
The authorization policy for this client: either `one_factor` or `two_factor`.
#### enforce_par
{{< confkey type="boolean" default="false" required="no" >}}
Enforces the use of a [Pushed Authorization Requests] flow for this client.
#### enforce_pkce
{{< confkey type="bool" default="false" required="no" >}}
This setting enforces the use of [PKCE] for this individual client. To enforce it for all clients see the global
[enforce_pkce](#enforcepkce) setting.
#### pkce_challenge_method
{{< confkey type="string" default="" required="no" >}}
This setting enforces the use of the specified [PKCE] challenge method for this individual client. This setting also
effectively enables the [enforce_pkce](#enforcepkce-1) option for this client.
Valid values are an empty string, `plain`, or `S256`. It should be noted that `S256` is strongly recommended if the
relying party supports it.
#### userinfo_signing_algorithm
{{< confkey type="string" default="none" required="no" >}}
The algorithm used to sign the userinfo endpoint responses. This can either be `none` or `RS256`.
See the [integration guide](../../integration/openid-connect/introduction.md#user-information-signing-algorithm) for
more information.
#### token_endpoint_auth_method
{{< confkey type="string" default="" required="no" >}}
The registered client authentication mechanism used by this client for the [Token Endpoint]. If no method is defined
the confidential client type will accept any supported method. The public client type defaults to `none` as this
is required by the specification. This may be required as a breaking change in future versions.
Supported values are `client_secret_basic`, `client_secret_post`, `client_secret_jwt`, and `none`.
See the [integration guide](../../integration/openid-connect/introduction.md#client-authentication-method) for
more information.
#### token_endpoint_auth_signing_alg
{{< confkey type="string" default="HS256" required="no" >}}
The JWT signing algorithm accepted when the [token_endpoint_auth_method](#tokenendpointauthmethod) is configured as
`client_secret_jwt`. Supported values are `HS256`, `HS385`, and `HS512`.
#### consent_mode
{{< confkey type="string" default="auto" required="no" >}}
*__Important Note:__ the `implicit` consent mode is not technically part of the specification. It theoretically could be
misused in certain conditions specifically with the public client type or when the client credentials (i.e. client
secret) has been exposed to an attacker. For these reasons this mode is discouraged.*
Configures the consent mode. The following table describes the different modes:
| Value | Description |
|:--------------:|:----------------------------------------------------------------------------------------------------------------------------------------------:|
| auto | Automatically determined (default). Uses `explicit` unless [pre_configured_consent_duration] is specified in which case uses `pre-configured`. |
| explicit | Requires the user provide unique explicit consent for every authorization. |
| implicit | Automatically assumes consent for every authorization, never asking the user if they wish to give consent. |
| pre-configured | Allows the end-user to remember their consent for the [pre_configured_consent_duration]. |
[pre_configured_consent_duration]: #preconfiguredconsentduration
#### pre_configured_consent_duration
{{< confkey type="duration" default="1w" required="no" >}}
*__Note:__ This setting uses the [duration notation format](../prologue/common.md#duration-notation-format). Please see
the [common options](../prologue/common.md#duration-notation-format) documentation for information on this format.*
Specifying this in the configuration without a consent [consent_mode] enables the `pre-configured` mode. If this is
specified as well as the [consent_mode] then it only has an effect if the [consent_mode] is `pre-configured` or `auto`.
The period of time dictates how long a users choice to remember the pre-configured consent lasts.
Pre-configured consents are only valid if the subject, client id are exactly the same and the requested scopes/audience
match exactly with the granted scopes/audience.
[consent_mode]: #consentmode
## Integration
To integrate Authelia's [OpenID Connect 1.0] implementation with a relying party please see the
[integration docs](../../integration/openid-connect/introduction.md).
[token lifespan]: https://docs.apigee.com/api-platform/antipatterns/oauth-long-expiration
[OpenID Connect 1.0]: https://openid.net/connect/
[Token Endpoint]: https://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint
[JWT]: https://datatracker.ietf.org/doc/html/rfc7519
[RFC6234]: https://datatracker.ietf.org/doc/html/rfc6234
[RFC4648]: https://datatracker.ietf.org/doc/html/rfc4648
[RFC7468]: https://datatracker.ietf.org/doc/html/rfc7468
[RFC6749 Section 2.1]: https://datatracker.ietf.org/doc/html/rfc6749#section-2.1
[PKCE]: https://datatracker.ietf.org/doc/html/rfc7636
[Authorization Code Flow]: https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth
[Subject Identifier Type]: https://openid.net/specs/openid-connect-core-1_0.html#SubjectIDTypes
[Pairwise Identifier Algorithm]: https://openid.net/specs/openid-connect-core-1_0.html#PairwiseAlg
[Pushed Authorization Requests]: https://datatracker.ietf.org/doc/html/rfc9126

View File

@ -0,0 +1,15 @@
---
title: "OpenID Connect 1.0"
description: ""
lead: ""
date: 2023-05-08T13:38:08+10:00
lastmod: 2022-01-18T20:07:56+01:00
draft: false
images: []
menu:
docs:
parent: "identity-providers"
identifier: "openid-connect"
weight: 190120
toc: true
---

View File

@ -0,0 +1,413 @@
---
title: "OpenID Connect 1.0 Clients"
description: "OpenID Connect 1.0 Registered Clients Configuration"
lead: "Authelia can operate as an OpenID Connect 1.0 Provider. This section describes how to configure the registered clients."
date: 2023-05-08T13:38:08+10:00
draft: false
images: []
menu:
configuration:
parent: "openid-connect"
weight: 190220
toc: true
---
This section covers specifics regarding configuring the providers registered clients for [OpenID Connect 1.0]. For the
provider specific configuration and information not related to clients see the [OpenID Connect 1.0 Provider](provider.md)
documentation.
More information about OpenID Connect can be found in the [roadmap](../../../roadmap/active/openid-connect.md) and in the
[integration](../../../integration/openid-connect/introduction.md) documentation.
## Configuration
The following snippet provides a configuration example for the [OpenID Connect 1.0] Registered Clients. This is not
intended for production use it's used to provide context and an indentation example.
```yaml
identity_providers:
oidc:
clients:
- id: myapp
description: My Application
secret: '$pbkdf2-sha512$310000$c8p78n7pUMln0jzvd4aK4Q$JNRBzwAo0ek5qKn50cFzzvE9RXV88h1wJn5KGiHrD0YKtZaR/nCb2CJPOsKaPK0hjf.9yHxzQGZziziccp6Yng' # The digest of 'insecure_secret'.
sector_identifier: ''
public: false
redirect_uris:
- https://oidc.example.com:8080/oauth2/callback
audience: []
scopes:
- openid
- groups
- email
- profile
grant_types:
- refresh_token
- authorization_code
response_types:
- code
response_modes:
- form_post
- query
- fragment
authorization_policy: two_factor
consent_mode: explicit
pre_configured_consent_duration: 1w
enforce_par: false
enforce_pkce: false
pkce_challenge_method: S256
token_endpoint_auth_method: ''
token_endpoint_auth_signing_alg: RS256
id_token_signing_alg: RS256
request_object_signing_alg: RS256
userinfo_signing_alg: none
```
## Options
### id
{{< confkey type="string" required="yes" >}}
The Client ID for this client. It must exactly match the Client ID configured in the application consuming this client.
### description
{{< confkey type="string" default="*same as id*" required="no" >}}
A friendly description for this client shown in the UI. This defaults to the same as the ID.
### secret
{{< confkey type="string" required="situational" >}}
The shared secret between Authelia and the application consuming this client. This secret must match the secret
configured in the application.
This secret must be generated by the administrator and can be done by following the
[How Do I Generate Client Secrets](../../../integration/openid-connect/frequently-asked-questions.md#how-do-i-generate-client-secrets) FAQ.
This must be provided when the client is a confidential client type, and must be blank when using the public client
type. To set the client type to public see the [public](#public) configuration option.
### sector_identifier
{{< confkey type="string" required="no" >}}
*__Important Note:__ because adjusting this option will inevitably change the `sub` claim of all tokens generated for
the specified client, changing this should cause the relying party to detect all future authorizations as completely new
users.*
Must be an empty string or the host component of a URL. This is commonly just the domain name, but may also include a
port.
Authelia utilizes UUID version 4 subject identifiers. By default the public [Subject Identifier Type] is utilized for
all clients. This means the subject identifiers will be the same for all clients. This configuration option enables
[Pairwise Identifier Algorithm] for this client, and configures the sector identifier utilized for both the storage and
the lookup of the subject identifier.
1. All clients who do not have this configured will generate the same subject identifier for a particular user
regardless of which client obtains the ID token.
2. All clients which have the same sector identifier will:
1. have the same subject identifier for a particular user when compared to clients with the same sector identifier.
2. have a completely different subject identifier for a particular user whe compared to:
1. any client with the public subject identifier type.
2. any client with a differing sector identifier.
In specific but limited scenarios this option is beneficial for privacy reasons. In particular this is useful when the
party utilizing the *Authelia* [OpenID Connect 1.0] Authorization Server is foreign and not controlled by the user. It would
prevent the third party utilizing the subject identifier with another third party in order to track the user.
Keep in mind depending on the other claims they may still be able to perform this tracking and it is not a silver
bullet. There are very few benefits when utilizing this in a homelab or business where no third party is utilizing
the server.
### public
{{< confkey type="bool" default="false" required="no" >}}
This enables the public client type for this client. This is for clients that are not capable of maintaining
confidentiality of credentials, you can read more about client types in [RFC6749 Section 2.1]. This is particularly
useful for SPA's and CLI tools. This option requires setting the [client secret](#secret) to a blank string.
### redirect_uris
{{< confkey type="list(string)" required="yes" >}}
A list of valid callback URIs this client will redirect to. All other callbacks will be considered unsafe. The URIs are
case-sensitive and they differ from application to application - the community has provided
[a list of URL´s for common applications](../../../integration/openid-connect/introduction.md).
Some restrictions that have been placed on clients and
their redirect URIs are as follows:
1. If a client attempts to authorize with Authelia and its redirect URI is not listed in the client configuration the
attempt to authorize will fail and an error will be generated.
2. The redirect URIs are case-sensitive.
3. The URI must include a scheme and that scheme must be one of `http` or `https`.
### audience
{{< confkey type="list(string)" required="no" >}}
A list of audiences this client is allowed to request.
### scopes
{{< confkey type="list(string)" default="openid, groups, profile, email" required="no" >}}
A list of scopes to allow this client to consume. See
[scope definitions](../../../integration/openid-connect/introduction.md#scope-definitions) for more information. The
documentation for the application you are trying to configure [OpenID Connect 1.0] for will likely have a list of scopes
or claims required which can be matched with the above guide.
### grant_types
{{< confkey type="list(string)" default="authorization_code" required="no" >}}
*__Important Note:__ It is recommended that this isn't configured at this time unless you know what you're doing.*
The list of grant types this client is permitted to use in order to obtain access to the relevant tokens.
See the [Grant Types](../../../integration/openid-connect/introduction.md#grant-types) section of the
[OpenID Connect 1.0 Integration Guide](../../../integration/openid-connect/introduction.md#grant-types) for more information.
### response_types
{{< confkey type="list(string)" default="code" required="no" >}}
*__Security Note:__ It is recommended that only the `code` response type (i.e. the default) is used. The other response
types are not as secure as this response type.*
A list of response types this client supports. If a response type not in this list is requested by a client then an
error will be returned to the client. The response type indicates the types of values that are returned to the client.
See the [Response Types](../../../integration/openid-connect/introduction.md#response-types) section of the
[OpenID Connect 1.0 Integration Guide](../../../integration/openid-connect/introduction.md#response-types) for more information.
### response_modes
{{< confkey type="list(string)" default="form_post, query" required="no" >}}
*__Important Note:__ It is recommended that this isn't configured at this time unless you know what you're doing.*
A list of response modes this client supports. If a response mode not in this list is requested by a client then an
error will be returned to the client. The response mode controls how the response type is returned to the client.
See the [Response Modes](../../../integration/openid-connect/introduction.md#response-modes) section of the
[OpenID Connect 1.0 Integration Guide](../../../integration/openid-connect/introduction.md#response-modes) for more
information.
The default values are based on the [response_types](#responsetypes) values. When the [response_types](#responsetypes)
values include the `code` type then the `query` response mode will be included. When any other type is included the
`fragment` response mode will be included. It's important to note at this time we do not support the `none` response
type, but when it is supported it will include the `query` response mode.
### authorization_policy
{{< confkey type="string" default="two_factor" required="no" >}}
The authorization policy for this client: either `one_factor` or `two_factor`.
### consent_mode
{{< confkey type="string" default="auto" required="no" >}}
*__Important Note:__ the `implicit` consent mode is not technically part of the specification. It theoretically could be
misused in certain conditions specifically with the public client type or when the client credentials (i.e. client
secret) has been exposed to an attacker. For these reasons this mode is discouraged.*
Configures the consent mode. The following table describes the different modes:
| Value | Description |
|:--------------:|:----------------------------------------------------------------------------------------------------------------------------------------------:|
| auto | Automatically determined (default). Uses `explicit` unless [pre_configured_consent_duration] is specified in which case uses `pre-configured`. |
| explicit | Requires the user provide unique explicit consent for every authorization. |
| implicit | Automatically assumes consent for every authorization, never asking the user if they wish to give consent. |
| pre-configured | Allows the end-user to remember their consent for the [pre_configured_consent_duration]. |
[pre_configured_consent_duration]: #preconfiguredconsentduration
### pre_configured_consent_duration
{{< confkey type="duration" default="1w" required="no" >}}
*__Note:__ This setting uses the [duration notation format](../../prologue/common.md#duration-notation-format). Please see
the [common options](../../prologue/common.md#duration-notation-format) documentation for information on this format.*
Specifying this in the configuration without a consent [consent_mode] enables the `pre-configured` mode. If this is
specified as well as the [consent_mode] then it only has an effect if the [consent_mode] is `pre-configured` or `auto`.
The period of time dictates how long a users choice to remember the pre-configured consent lasts.
Pre-configured consents are only valid if the subject, client id are exactly the same and the requested scopes/audience
match exactly with the granted scopes/audience.
[consent_mode]: #consentmode
### enforce_par
{{< confkey type="boolean" default="false" required="no" >}}
This configuration option enforces the use of a [Pushed Authorization Requests] flow for this registered client.
To enforce it for all clients see the global [pushed_authorizations enforce](provider.md#enforce) provider configuration
option.
### enforce_pkce
{{< confkey type="bool" default="false" required="no" >}}
This configuration option enforces the use of [PKCE] for this registered client. To enforce it for all clients see the
global [enforce_pkce](provider.md#enforcepkce) provider configuration option.
### pkce_challenge_method
{{< confkey type="string" default="" required="no" >}}
This setting enforces the use of the specified [PKCE] challenge method for this individual client. This setting also
effectively enables the [enforce_pkce](#enforcepkce) option for this client.
Valid values are an empty string, `plain`, or `S256`. It should be noted that `S256` is strongly recommended if the
relying party supports it.
### token_endpoint_auth_method
{{< confkey type="string" default="" required="no" >}}
The registered client authentication mechanism used by this client for the [Token Endpoint]. If no method is defined
the confidential client type will accept any supported method. The public client type defaults to `none` as this
is required by the specification. This may be required as a breaking change in future versions.
Supported values are `client_secret_basic`, `client_secret_post`, `client_secret_jwt`, `private_key_jwt`, and `none`.
See the [integration guide](../../../integration/openid-connect/introduction.md#client-authentication-method) for
more information.
### token_endpoint_auth_signing_alg
{{< confkey type="string" default="RS256" required="no" >}}
The JWT signing algorithm accepted when the [token_endpoint_auth_method](#tokenendpointauthmethod) is configured as
`client_secret_jwt` or `private_key_jwt`.
See the request object section of the [integration guide](../../../integration/openid-connect/introduction.md#request-object)
for more information including the algorithm column for supported values.
It's recommended that you specifically configure this when the following options are configured to specific values
otherwise we assume the default value:
| Configuration Option | Value | Default |
|:----------------------------------------------------------:|:-------------------:|:-------:|
| [token_endpoint_auth_method](#tokenendpointauthsigningalg) | `private_key_jwt` | `RS256` |
| [token_endpoint_auth_method](#tokenendpointauthsigningalg) | `client_secret_jwt` | `HS256` |
### request_object_signing_alg
{{< confkey type="string" default="RSA256" required="no" >}}
The JWT signing algorithm accepted for request objects.
See the request object section of the [integration guide](../../../integration/openid-connect/introduction.md#request-object)
for more information including the algorithm column for supported values.
### id_token_signing_alg
{{< confkey type="string" default="RS256" required="no" >}}
The algorithm used to sign the ID Tokens in the token responses.
See the response object section of the [integration guide](../../../integration/openid-connect/introduction.md#response-object)
for more information including the algorithm column for supported values. In addition to the values listed we also
support `none` as a value for this endpoint.
### userinfo_signing_alg
{{< confkey type="string" default="none" required="no" >}}
The algorithm used to sign the userinfo endpoint responses.
See the response object section of the [integration guide](../../../integration/openid-connect/introduction.md#response-object)
for more information including the algorithm column for supported values. In addition to the values listed we also
support `none` as a value for this endpoint.
### public_keys
This section configures the trusted JSON Web Keys or JWKS for this registered client. This can either be static values
(recommended) or a URI using the `https` scheme. This section is situational required. These are used to validate the
[JWT] assertions from clients.
Required when the following options are configured:
- [request_object_signing_alg](#requestobjectsigningalg)
- [token_endpoint_auth_signing_alg](#tokenendpointauthsigningalg)
Required when the following options are configured to specific values:
- [token_endpoint_auth_method](#tokenendpointauthsigningalg): `private_key_jwt`
#### uri
{{< confkey type="string" required="no" >}}
The fully qualified, `https` scheme, and appropriately signed URI for the JWKS endpoint that implements
[RFC7517 Section 5](https://datatracker.ietf.org/doc/html/rfc7517#section-5). Must not be configured at the same time
as [values](#values). It's recommended that you do not configure this option, but statically configure [values](#values)
instead.
*__Important Note:__ the URL given in this value MUST be resolvable by Authelia and MUST present a certificate signed by
a certificate trusted by your environment. It is beyond our intentions to support anything other than this.*
#### values
{{< confkey type="list(object)" required="situational" >}}
A list of static keys.
##### key_id
{{< confkey type="string" required="yes" >}}
The Key ID used to match the request object's JWT header `kid` value against.
##### key
{{< confkey type="string" required="yes" >}}
The public key portion of the JSON Web Key
The public key the clients use to sign/encrypt the [OpenID Connect 1.0] asserted [JWT]'s. The key is generated by the
client application or the administrator of the client application.
The key *__MUST__*:
* Be a PEM block encoded in the DER base64 format ([RFC4648]).
* Be either:
* An RSA public key:
* With a key size of at least 2048 bits.
* An ECDSA public key with one of:
* A P-256 elliptical curve.
* A P-384 elliptical curve.
* A P-512 elliptical curve.
If the [issuer_certificate_chain](#issuercertificatechain) is provided the private key must include matching public
key data for the first certificate in the chain.
## Integration
To integrate Authelia's [OpenID Connect 1.0] implementation with a relying party please see the
[integration docs](../../../integration/openid-connect/introduction.md).
[token lifespan]: https://docs.apigee.com/api-platform/antipatterns/oauth-long-expiration
[OpenID Connect 1.0]: https://openid.net/connect/
[Token Endpoint]: https://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint
[JWT]: https://datatracker.ietf.org/doc/html/rfc7519
[RFC6234]: https://datatracker.ietf.org/doc/html/rfc6234
[RFC4648]: https://datatracker.ietf.org/doc/html/rfc4648
[RFC7468]: https://datatracker.ietf.org/doc/html/rfc7468
[RFC6749 Section 2.1]: https://datatracker.ietf.org/doc/html/rfc6749#section-2.1
[PKCE]: https://datatracker.ietf.org/doc/html/rfc7636
[Authorization Code Flow]: https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth
[Subject Identifier Type]: https://openid.net/specs/openid-connect-core-1_0.html#SubjectIDTypes
[Pairwise Identifier Algorithm]: https://openid.net/specs/openid-connect-core-1_0.html#PairwiseAlg
[Pushed Authorization Requests]: https://datatracker.ietf.org/doc/html/rfc9126

View File

@ -0,0 +1,428 @@
---
title: "OpenID Connect 1.0 Provider"
description: "OpenID Connect 1.0 Provider Configuration"
lead: "Authelia can operate as an OpenID Connect 1.0 Provider. This section describes how to configure this."
date: 2023-05-08T13:38:08+10:00
draft: false
images: []
menu:
configuration:
parent: "openid-connect"
weight: 190200
toc: true
aliases:
- /c/oidc
- /docs/configuration/identity-providers/oidc.html
---
__Authelia__ currently supports the [OpenID Connect 1.0] Provider role as an open
[__beta__](../../../roadmap/active/openid-connect.md) feature. We currently do not support the [OpenID Connect 1.0] Relying
Party role. This means other applications that implement the [OpenID Connect 1.0] Relying Party role can use Authelia as
an [OpenID Connect 1.0] Provider similar to how you may use social media or development platforms for login.
The [OpenID Connect 1.0] Relying Party role is the role which allows an application to use GitHub, Google, or other
[OpenID Connect 1.0] Providers for authentication and authorization. We do not intend to support this functionality at
this moment in time.
This section covers the [OpenID Connect 1.0] Provider configuration. For information on configuring individual
registered clients see the [OpenID Connect 1.0 Clients](clients.md) documentation.
More information about the beta can be found in the [roadmap](../../../roadmap/active/openid-connect.md) and in the
[integration](../../../integration/openid-connect/introduction.md) documentation.
## Configuration
The following snippet provides a configuration example for the [OpenID Connect 1.0] Provider. This is not
intended for production use it's used to provide context and an indentation example.
```yaml
identity_providers:
oidc:
hmac_secret: this_is_a_secret_abc123abc123abc
issuer_private_keys:
- key_id: example
algorithm: RS256
use: sig
key: |
-----BEGIN RSA PUBLIC KEY-----
MEgCQQDAwV26ZA1lodtOQxNrJ491gWT+VzFum9IeZ+WTmMypYWyW1CzXKwsvTHDz
9ec+jserR3EMQ0Rr24lj13FL1ib5AgMBAAE=
-----END RSA PUBLIC KEY----
certificate_chain: |
-----BEGIN CERTIFICATE-----
MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT
MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3
NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00
z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8
Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx
1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT
MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3
NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00
z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8
Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx
1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE=
-----END CERTIFICATE-----
issuer_private_key: |
-----BEGIN RSA PUBLIC KEY-----
MEgCQQDAwV26ZA1lodtOQxNrJ491gWT+VzFum9IeZ+WTmMypYWyW1CzXKwsvTHDz
9ec+jserR3EMQ0Rr24lj13FL1ib5AgMBAAE=
-----END RSA PUBLIC KEY----
issuer_certificate_chain: |
-----BEGIN CERTIFICATE-----
MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT
MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3
NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00
z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8
Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx
1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT
MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3
NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00
z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8
Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx
1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE=
-----END CERTIFICATE-----
access_token_lifespan: 1h
authorize_code_lifespan: 1m
id_token_lifespan: 1h
refresh_token_lifespan: 90m
enable_client_debug_messages: false
minimum_parameter_entropy: 8
enforce_pkce: public_clients_only
enable_pkce_plain_challenge: false
pushed_authorizations:
enforce: false
context_lifespan: 5m
cors:
endpoints:
- authorization
- token
- revocation
- introspection
allowed_origins:
- https://example.com
allowed_origins_from_client_redirect_uris: false
```
## Options
### hmac_secret
{{< confkey type="string" required="yes" >}}
*__Important Note:__ This can also be defined using a [secret](../../methods/secrets.md) which is __strongly recommended__
especially for containerized deployments.*
The HMAC secret used to sign the [JWT]'s. The provided string is hashed to a SHA256 ([RFC6234]) byte string for the
purpose of meeting the required format.
It's __strongly recommended__ this is a
[Random Alphanumeric String](../../../reference/guides/generating-secure-values.md#generating-a-random-alphanumeric-string)
with 64 or more characters.
### issuer_private_keys
The key *__MUST__*:
* Be a PEM block encoded in the DER base64 format ([RFC4648]).
* Be either:
* An RSA public key:
* With a key size of at least 2048 bits.
* An ECDSA public key with one of:
* A P-256 elliptical curve.
* A P-384 elliptical curve.
* A P-512 elliptical curve.
### issuer_private_keys
{{< confkey type="list(object" required="no" >}}
The list of JWKS instead of or in addition to the [issuer_private_key](#issuerprivatekey) and
[issuer_certificate_chain](#issuercertificatechain). Can also accept ECDSA Private Key's and Certificates.
#### key_id
{{< confkey type="string" default="<thumbprint of public key>" required="no" >}}
Completely optional, and generally discouraged unless there is a collision between the automatically generated key id's.
If provided must be a unique string with 7 or less alphanumeric characters.
This value is the first 7 characters of the public key thumbprint (SHA1) encoded into hexadecimal.
#### algorithm
{{< confkey type="string" default="RS256" required="no" >}}
The algorithm for this key. This value must be unique. It's automatically detected based on the type of key.
See the response object table in the [integration guide](../../../integration/openid-connect/introduction.md#response-object)
including the algorithm column for the supported values and the key type column for the default algorithm value.
#### use
{{< confkey type="string" default="sig" required="no" >}}
The key usage. Defaults to `sig` which is the only available option at this time.
#### key
{{< confkey type="string" required="yes" >}}
The private key associated with this key entry.
The private key used to sign/encrypt the [OpenID Connect 1.0] issued [JWT]'s. The key must be generated by the administrator
and can be done by following the
[Generating an RSA Keypair](../../../reference/guides/generating-secure-values.md#generating-an-rsa-keypair) guide.
The private key *__MUST__*:
* Be a PEM block encoded in the DER base64 format ([RFC4648]).
* Be one of:
* An RSA key with a key size of at least 2048 bits.
* An ECDSA private key with one of the P-256, P-384, or P-521 elliptical curves.
If the [certificate_chain](#certificatechain) is provided the private key must include matching public
key data for the first certificate in the chain.
#### certificate_chain
{{< confkey type="string" required="no" >}}
The certificate chain/bundle to be used with the [key](#key) DER base64 ([RFC4648])
encoded PEM format used to sign/encrypt the [OpenID Connect 1.0] [JWT]'s. When configured it enables the [x5c] and [x5t]
JSON key's in the JWKs [Discoverable Endpoint](../../../integration/openid-connect/introduction.md#discoverable-endpoints)
as per [RFC7517].
[RFC7517]: https://datatracker.ietf.org/doc/html/rfc7517
[x5c]: https://datatracker.ietf.org/doc/html/rfc7517#section-4.7
[x5t]: https://datatracker.ietf.org/doc/html/rfc7517#section-4.8
The first certificate in the chain must have the public key for the [key](#key), each certificate in the chain must be
valid for the current date, and each certificate in the chain should be signed by the certificate immediately following
it if present.
### issuer_private_key
{{< confkey type="string" required="yes" >}}
*__Important Note:__ This can also be defined using a [secret](../../methods/secrets.md) which is __strongly recommended__
especially for containerized deployments.*
The private key used to sign/encrypt the [OpenID Connect 1.0] issued [JWT]'s. The key must be generated by the administrator
and can be done by following the
[Generating an RSA Keypair](../../../reference/guides/generating-secure-values.md#generating-an-rsa-keypair) guide.
This private key is automatically appended to the [issuer_private_keys](#issuerprivatekeys) and assumed to be for the
RS256 algorithm. As such no other key in this list should be RS256 if this is configured.
The issuer private key *__MUST__*:
* Be a PEM block encoded in the DER base64 format ([RFC4648]).
* Be an RSA private key:
* With a key size of at least 2048 bits.
If the [issuer_certificate_chain](#issuercertificatechain) is provided the private key must include matching public
key data for the first certificate in the chain.
### issuer_certificate_chain
{{< confkey type="string" required="no" >}}
The certificate chain/bundle to be used with the [issuer_private_key](#issuer_private_key) DER base64 ([RFC4648])
encoded PEM format used to sign/encrypt the [OpenID Connect 1.0] [JWT]'s. When configured it enables the [x5c] and [x5t]
JSON key's in the JWKs [Discoverable Endpoint](../../../integration/openid-connect/introduction.md#discoverable-endpoints)
as per [RFC7517].
[RFC7517]: https://datatracker.ietf.org/doc/html/rfc7517
[x5c]: https://datatracker.ietf.org/doc/html/rfc7517#section-4.7
[x5t]: https://datatracker.ietf.org/doc/html/rfc7517#section-4.8
The first certificate in the chain must have the public key for the [issuer_private_key](#issuerprivatekey), each
certificate in the chain must be valid for the current date, and each certificate in the chain should be signed by the
certificate immediately following it if present.
### access_token_lifespan
{{< confkey type="duration" default="1h" required="no" >}}
The maximum lifetime of an access token. It's generally recommended keeping this short similar to the default.
For more information read these docs about [token lifespan].
### authorize_code_lifespan
{{< confkey type="duration" default="1m" required="no" >}}
The maximum lifetime of an authorize code. This can be rather short, as the authorize code should only be needed to
obtain the other token types. For more information read these docs about [token lifespan].
### id_token_lifespan
{{< confkey type="duration" default="1h" required="no" >}}
The maximum lifetime of an ID token. For more information read these docs about [token lifespan].
### refresh_token_lifespan
{{< confkey type="string" default="90m" required="no" >}}
The maximum lifetime of a refresh token. The
refresh token can be used to obtain new refresh tokens as well as access tokens or id tokens with an
up-to-date expiration. For more information read these docs about [token lifespan].
A good starting point is 50% more or 30 minutes more (which ever is less) time than the highest lifespan out of the
[access token lifespan](#accesstokenlifespan), the [authorize code lifespan](#authorizecodelifespan), and the
[id token lifespan](#idtokenlifespan). For instance the default for all of these is 60 minutes, so the default refresh
token lifespan is 90 minutes.
### enable_client_debug_messages
{{< confkey type="boolean" default="false" required="no" >}}
Allows additional debug messages to be sent to the clients.
### minimum_parameter_entropy
{{< confkey type="integer" default="8" required="no" >}}
This controls the minimum length of the `nonce` and `state` parameters.
*__Security Notice:__* Changing this value is generally discouraged, reducing it from the default can theoretically
make certain scenarios less secure. It is highly encouraged that if your OpenID Connect RP does not send these
parameters or sends parameters with a lower length than the default that they implement a change rather than changing
this value.
### enforce_pkce
{{< confkey type="string" default="public_clients_only" required="no" >}}
[Proof Key for Code Exchange](https://datatracker.ietf.org/doc/html/rfc7636) enforcement policy: if specified, must be
either `never`, `public_clients_only` or `always`.
If set to `public_clients_only` (default), [PKCE] will be required for public clients using the
[Authorization Code Flow].
When set to `always`, [PKCE] will be required for all clients using the Authorization Code flow.
*__Security Notice:__* Changing this value to `never` is generally discouraged, reducing it from the default can
theoretically make certain client-side applications (mobile applications, SPA) vulnerable to CSRF and authorization code
interception attacks.
### enable_pkce_plain_challenge
{{< confkey type="boolean" default="false" required="no" >}}
Allows [PKCE] `plain` challenges when set to `true`.
*__Security Notice:__* Changing this value is generally discouraged. Applications should use the `S256` [PKCE] challenge
method instead.
### pushed_authorizations
Controls the behaviour of [Pushed Authorization Requests].
#### enforce
{{< confkey type="boolean" default="false" required="no" >}}
When enabled all authorization requests must use the [Pushed Authorization Requests] flow.
#### context_lifespan
{{< confkey type="duration" default="5m" required="no" >}}
The maximum amount of time between the [Pushed Authorization Requests] flow being initiated and the generated
`request_uri` being utilized by a client.
### cors
Some [OpenID Connect 1.0] Endpoints need to allow cross-origin resource sharing, however some are optional. This section allows
you to configure the optional parts. We reply with CORS headers when the request includes the Origin header.
#### endpoints
{{< confkey type="list(string)" required="no" >}}
A list of endpoints to configure with cross-origin resource sharing headers. It is recommended that the `userinfo`
option is at least in this list. The potential endpoints which this can be enabled on are as follows:
* authorization
* pushed-authorization-request
* token
* revocation
* introspection
* userinfo
#### allowed_origins
{{< confkey type="list(string)" required="no" >}}
A list of permitted origins.
Any origin with https is permitted unless this option is configured or the
[allowed_origins_from_client_redirect_uris](#allowedoriginsfromclientredirecturis) option is enabled. This means
you must configure this option manually if you want http endpoints to be permitted to make cross-origin requests to the
[OpenID Connect 1.0] endpoints, however this is not recommended.
Origins must only have the scheme, hostname and port, they may not have a trailing slash or path.
In addition to an Origin URI, you may specify the wildcard origin in the allowed_origins. It MUST be specified by itself
and the [allowed_origins_from_client_redirect_uris](#allowedoriginsfromclientredirecturis) MUST NOT be enabled. The
wildcard origin is denoted as `*`. Examples:
```yaml
identity_providers:
oidc:
cors:
allowed_origins: "*"
```
```yaml
identity_providers:
oidc:
cors:
allowed_origins:
- "*"
```
#### allowed_origins_from_client_redirect_uris
{{< confkey type="boolean" default="false" required="no" >}}
Automatically adds the origin portion of all redirect URI's on all clients to the list of
[allowed_origins](#allowed_origins), provided they have the scheme http or https and do not have the hostname of
localhost.
### clients
See the [OpenID Connect 1.0 Registered Clients](clients.md) documentation for configuring clients.
## Integration
To integrate Authelia's [OpenID Connect 1.0] implementation with a relying party please see the
[integration docs](../../integration/openid-connect/introduction.md).
[token lifespan]: https://docs.apigee.com/api-platform/antipatterns/oauth-long-expiration
[OpenID Connect 1.0]: https://openid.net/connect/
[Token Endpoint]: https://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint
[JWT]: https://datatracker.ietf.org/doc/html/rfc7519
[RFC6234]: https://datatracker.ietf.org/doc/html/rfc6234
[RFC4648]: https://datatracker.ietf.org/doc/html/rfc4648
[RFC7468]: https://datatracker.ietf.org/doc/html/rfc7468
[RFC6749 Section 2.1]: https://datatracker.ietf.org/doc/html/rfc6749#section-2.1
[PKCE]: https://datatracker.ietf.org/doc/html/rfc7636
[Authorization Code Flow]: https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth
[Subject Identifier Type]: https://openid.net/specs/openid-connect-core-1_0.html#SubjectIDTypes
[Pairwise Identifier Algorithm]: https://openid.net/specs/openid-connect-core-1_0.html#PairwiseAlg
[Pushed Authorization Requests]: https://datatracker.ietf.org/doc/html/rfc9126

View File

@ -77,9 +77,9 @@ other configuration using the environment but instead of loading a file the valu
[authentication_backend.ldap.password]: ../first-factor/ldap.md#password
[authentication_backend.ldap.tls.certificate_chain]: ../first-factor/ldap.md#tls
[authentication_backend.ldap.tls.private_key]: ../first-factor/ldap.md#tls
[identity_providers.oidc.issuer_certificate_chain]: ../identity-providers/open-id-connect.md#issuercertificatechain
[identity_providers.oidc.issuer_private_key]: ../identity-providers/open-id-connect.md#issuerprivatekey
[identity_providers.oidc.hmac_secret]: ../identity-providers/open-id-connect.md#hmacsecret
[identity_providers.oidc.issuer_certificate_chain]: ../identity-providers/openid-connect.md#issuercertificatechain
[identity_providers.oidc.issuer_private_key]: ../identity-providers/openid-connect.md#issuerprivatekey
[identity_providers.oidc.hmac_secret]: ../identity-providers/openid-connect.md#hmacsecret
## Secrets in configuration file

View File

@ -44,7 +44,7 @@ accepted is recorded and checked in the browser
If the user has not accepted the policy they should not be able to interact with the Authelia UI via normal means.
Administrators who are required to abide by the [GDPR] or other privacy laws should be advised that
[OpenID Connect 1.0](../identity-providers/open-id-connect.md) clients configured with the `implicit` consent mode are
[OpenID Connect 1.0](../identity-providers/openid-connect.md) clients configured with the `implicit` consent mode are
unlikely to trigger the display of the Authelia UI if the user is already authenticated.
We wont be adding checks like this to the `implicit` consent mode when that mode in particular is unlikely to be

View File

@ -53,7 +53,7 @@ openid-groups-claim-type: groups
### Authelia
The following YAML configuration is an example __Authelia__
[client configuration](../../../configuration/identity-providers/open-id-connect.md#clients) for use with
[client configuration](../../../configuration/identity-providers/openid-connect/clients.md) for use with
[Apache Guacamole] which will operate with the above example:
```yaml
@ -78,7 +78,7 @@ identity_providers:
- 'id_token'
grant_types:
- 'implicit'
userinfo_signing_algorithm: 'none'
userinfo_signing_alg: 'none'
```
## See Also

View File

@ -56,7 +56,7 @@ requestedScopes:
### Authelia
The following YAML configuration is an example __Authelia__
[client configuration](../../../configuration/identity-providers/open-id-connect.md#clients) for use with [Argo CD]
[client configuration](../../../configuration/identity-providers/openid-connect/clients.md) for use with [Argo CD]
which will operate with the above example:
```yaml
@ -77,7 +77,7 @@ identity_providers:
- 'groups'
- 'email'
- 'profile'
userinfo_signing_algorithm: 'none'
userinfo_signing_alg: 'none'
- id: 'argocd-cli'
description: 'Argo CD (CLI)'
public: true
@ -90,7 +90,7 @@ identity_providers:
- 'email'
- 'profile'
- 'offline_access'
userinfo_signing_algorithm: 'none'
userinfo_signing_alg: 'none'
```
## See Also

View File

@ -58,7 +58,7 @@ To configure [BookStack] to utilize Authelia as an [OpenID Connect 1.0] Provider
### Authelia
The following YAML configuration is an example __Authelia__
[client configuration](../../../configuration/identity-providers/open-id-connect.md#clients) for use with [BookStack]
[client configuration](../../../configuration/identity-providers/openid-connect/clients.md) for use with [BookStack]
which will operate with the above example:
```yaml
@ -78,7 +78,7 @@ identity_providers:
- 'openid'
- 'profile'
- 'email'
userinfo_signing_algorithm: 'none'
userinfo_signing_alg: 'none'
```
## See Also

View File

@ -66,7 +66,7 @@ To configure [Cloudflare Zero Trust] to utilize Authelia as an [OpenID Connect 1
### Authelia
The following YAML configuration is an example __Authelia__
[client configuration](../../../configuration/identity-providers/open-id-connect.md#clients) for use with [Cloudflare]
[client configuration](../../../configuration/identity-providers/openid-connect/clients.md) for use with [Cloudflare]
which will operate with the above example:
```yaml
@ -86,7 +86,7 @@ identity_providers:
- 'openid'
- 'profile'
- 'email'
userinfo_signing_algorithm: 'none'
userinfo_signing_alg: 'none'
```
## See Also

View File

@ -67,7 +67,7 @@ descriptions.
### Authelia
The following YAML configuration is an example __Authelia__
[client configuration](../../../configuration/identity-providers/open-id-connect.md#clients) for use with [Firezone] which
[client configuration](../../../configuration/identity-providers/openid-connect/clients.md) for use with [Firezone] which
will operate with the above example:
```yaml
@ -89,7 +89,7 @@ identity_providers:
- 'openid'
- 'email'
- 'profile'
userinfo_signing_algorithm: 'none'
userinfo_signing_alg: 'none'
```
## See Also

View File

@ -77,7 +77,7 @@ descriptions.
### Authelia
The following YAML configuration is an example __Authelia__
[client configuration](../../../configuration/identity-providers/open-id-connect.md#clients) for use with [Gitea] which
[client configuration](../../../configuration/identity-providers/openid-connect/clients.md) for use with [Gitea] which
will operate with the above example:
```yaml
@ -97,7 +97,7 @@ identity_providers:
- 'openid'
- 'email'
- 'profile'
userinfo_signing_algorithm: 'none'
userinfo_signing_alg: 'none'
```
## See Also

View File

@ -69,7 +69,7 @@ gitlab_rails['omniauth_providers'] = [
### Authelia
The following YAML configuration is an example __Authelia__
[client configuration](../../../configuration/identity-providers/open-id-connect.md#clients) for use with [GitLab]
[client configuration](../../../configuration/identity-providers/openid-connect/clients.md) for use with [GitLab]
which will operate with the above example:
```yaml
@ -90,7 +90,7 @@ identity_providers:
- 'profile'
- 'groups'
- 'email'
userinfo_signing_algorithm: 'none'
userinfo_signing_alg: 'none'
```
## See Also

View File

@ -87,7 +87,7 @@ Configure the following environment variables:
### Authelia
The following YAML configuration is an example __Authelia__
[client configuration](../../../configuration/identity-providers/open-id-connect.md#clients) for use with [Grafana]
[client configuration](../../../configuration/identity-providers/openid-connect/clients.md) for use with [Grafana]
which will operate with the above example:
```yaml
@ -108,7 +108,7 @@ identity_providers:
- 'profile'
- 'groups'
- 'email'
userinfo_signing_algorithm: 'none'
userinfo_signing_alg: 'none'
```
## See Also

View File

@ -60,7 +60,7 @@ To configure [Harbor] to utilize Authelia as an [OpenID Connect 1.0] Provider:
### Authelia
The following YAML configuration is an example __Authelia__
[client configuration](../../../configuration/identity-providers/open-id-connect.md#clients) for use with [Harbor]
[client configuration](../../../configuration/identity-providers/openid-connect/clients.md) for use with [Harbor]
which will operate with the above example:
```yaml
@ -81,7 +81,7 @@ identity_providers:
- 'profile'
- 'groups'
- 'email'
userinfo_signing_algorithm: 'none'
userinfo_signing_alg: 'none'
```
## See Also

View File

@ -43,7 +43,7 @@ To configure [HashiCorp Vault] to utilize Authelia as an [OpenID Connect 1.0] Pr
### Authelia
The following YAML configuration is an example __Authelia__
[client configuration](../../../configuration/identity-providers/open-id-connect.md#clients) for use with [HashiCorp Vault]
[client configuration](../../../configuration/identity-providers/openid-connect/clients.md) for use with [HashiCorp Vault]
which will operate with the above example:
```yaml
@ -65,7 +65,7 @@ identity_providers:
- 'profile'
- 'groups'
- 'email'
userinfo_signing_algorithm: 'none'
userinfo_signing_alg: 'none'
```
## See Also

View File

@ -18,8 +18,10 @@ Authelia can act as an [OpenID Connect 1.0] Provider as part of an open beta. Th
specifics that can be used for integrating Authelia with an [OpenID Connect 1.0] Relying Party, as well as specific
documentation for some [OpenID Connect 1.0] Relying Party implementations.
See the [configuration documentation](../../configuration/identity-providers/open-id-connect.md) for information on how
to configure the Authelia [OpenID Connect 1.0] Provider.
See the [OpenID Connect 1.0 Provider](../../configuration/identity-providers/openid-connect/provider.md) and
[OpenID Connect 1.0 Clients](../../configuration/identity-providers/openid-connect/clients.md) configuration guides for
information on how to configure the Authelia [OpenID Connect 1.0] Provider (note the clients guide is for configuring
the registered clients in the provider).
This page is intended as an integration reference point for any implementers who wish to integrate an
[OpenID Connect 1.0] Relying Party (client application) either as a developer or user of the third party Reyling Party.
@ -124,6 +126,7 @@ Authelia's response objects can have the following signature algorithms:
### Request Object
Authelia accepts a wide variety of request object types.
| Algorithm | Key Type | Hashing Algorithm | Use | Notes |
|:---------:|:------------------:|:-----------------:|:---------:|:--------------------------------------------------:|
@ -131,6 +134,15 @@ Authelia's response objects can have the following signature algorithms:
| HS256 | HMAC Shared Secret | SHA-256 | Signature | [Client Authentication Method] `client_secret_jwt` |
| HS384 | HMAC Shared Secret | SHA-384 | Signature | [Client Authentication Method] `client_secret_jwt` |
| HS512 | HMAC Shared Secret | SHA-512 | Signature | [Client Authentication Method] `client_secret_jwt` |
| RS256 | RSA | SHA-256 | Signature | [Client Authentication Method] `private_key_jwt` |
| RS384 | RSA | SHA-384 | Signature | [Client Authentication Method] `private_key_jwt` |
| RS512 | RSA | SHA-512 | Signature | [Client Authentication Method] `private_key_jwt` |
| ES256 | ECDSA P-256 | SHA-256 | Signature | [Client Authentication Method] `private_key_jwt` |
| ES384 | ECDSA P-384 | SHA-384 | Signature | [Client Authentication Method] `private_key_jwt` |
| ES512 | ECDSA P-521 | SHA-512 | Signature | [Client Authentication Method] `private_key_jwt` |
| PS256 | RSA (MFG1) | SHA-256 | Signature | [Client Authentication Method] `private_key_jwt` |
| PS384 | RSA (MFG1) | SHA-384 | Signature | [Client Authentication Method] `private_key_jwt` |
| PS512 | RSA (MFG1) | SHA-512 | Signature | [Client Authentication Method] `private_key_jwt` |
[Client Authentication Method]: #client-authentication-method
@ -208,7 +220,7 @@ specification and the [OAuth 2.0 - Client Types] specification for more informat
| Secret via HTTP Basic Auth Scheme | `client_secret_basic` | `confidential` | N/A | N/A |
| Secret via HTTP POST Body | `client_secret_post` | `confidential` | N/A | N/A |
| JWT (signed by secret) | `client_secret_jwt` | `confidential` | N/A | `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` |
| JWT (signed by private key) | `private_key_jwt` | Not Supported | N/A | `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` |
| JWT (signed by private key) | `private_key_jwt` | `confidential` | N/A | `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` |
| [OAuth 2.0 Mutual-TLS] | `tls_client_auth` | Not Supported | N/A | N/A |
| [OAuth 2.0 Mutual-TLS] (Self Signed) | `self_signed_tls_client_auth` | Not Supported | N/A | N/A |
| No Authentication | `none` | `public` | `public` | N/A |
@ -243,7 +255,7 @@ Below is a list of the potential values we place in the [Claim] and their meanin
## User Information Signing Algorithm
The following table describes the response from the [UserInfo] endpoint depending on the
[userinfo_signing_algorithm](../../configuration/identity-providers/open-id-connect.md#userinfosigningalgorithm).
[userinfo_signing_alg](../../configuration/identity-providers/openid-connect/clients.md#userinfosigningalg).
| Signing Algorithm | Encoding | Content Type |
|:-----------------:|:------------:|:-----------------------------------:|

View File

@ -80,7 +80,7 @@ identity_providers:
- 'groups'
- 'email'
consent_mode: 'implicit'
userinfo_signing_algorithm: 'none'
userinfo_signing_alg: 'none'
```
## See Also

View File

@ -65,7 +65,7 @@ spring:
### Authelia
The following YAML configuration is an example __Authelia__
[client configuration](../../../configuration/identity-providers/open-id-connect.md#clients) for use with [Komga]
[client configuration](../../../configuration/identity-providers/openid-connect/clients.md) for use with [Komga]
which will operate with the above example:
```yaml
@ -87,7 +87,7 @@ identity_providers:
- 'email'
grant_types:
- 'authorization_code'
userinfo_signing_algorithm: 'none'
userinfo_signing_alg: 'none'
```
## See Also

View File

@ -63,7 +63,7 @@ To configure [MinIO] to utilize Authelia as an [OpenID Connect 1.0] Provider:
### Authelia
The following YAML configuration is an example __Authelia__
[client configuration](../../../configuration/identity-providers/open-id-connect.md#clients) for use with [MinIO]
[client configuration](../../../configuration/identity-providers/openid-connect/clients.md) for use with [MinIO]
which will operate with the above example:
```yaml
@ -84,7 +84,7 @@ identity_providers:
- 'profile'
- 'email'
- 'groups'
userinfo_signing_algorithm: 'none'
userinfo_signing_alg: 'none'
```
## See Also

View File

@ -79,7 +79,7 @@ To configure [Misago] to utilize Authelia as an [OpenID Connect 1.0](https://www
### Authelia
The following YAML configuration is an example **Authelia** [client configuration](https://www.authelia.com/configuration/identity-providers/open-id-connect/#clients) for use with [Misago] which will operate with the above example:
The following YAML configuration is an example **Authelia** [client configuration](https://www.authelia.com/configuration/identity-providers/openid-connect/#clients) for use with [Misago] which will operate with the above example:
```yaml
identity_providers:
@ -104,7 +104,7 @@ identity_providers:
- 'code'
response_modes:
- 'query'
userinfo_signing_algorithm: 'none'
userinfo_signing_alg: 'none'
```
---

View File

@ -86,7 +86,7 @@ $CONFIG = array (
### Authelia
The following YAML configuration is an example __Authelia__
[client configuration](../../../configuration/identity-providers/open-id-connect.md#clients) for use with [Nextcloud]
[client configuration](../../../configuration/identity-providers/openid-connect/clients.md) for use with [Nextcloud]
which will operate with the above example:
```yaml
@ -107,7 +107,7 @@ identity_providers:
- 'profile'
- 'email'
- 'groups'
userinfo_signing_algorithm: 'none'
userinfo_signing_alg: 'none'
```
## See Also

View File

@ -60,7 +60,7 @@ OIDC_SCOPES="openid offline_access profile email"
### Authelia
The following YAML configuration is an example __Authelia__
[client configuration](../../../configuration/identity-providers/open-id-connect.md#clients) for use with [Outline]
[client configuration](../../../configuration/identity-providers/openid-connect/clients.md) for use with [Outline]
which will operate with the above example:
```yaml
@ -81,7 +81,7 @@ identity_providers:
- 'offline_access'
- 'profile'
- 'email'
userinfo_signing_algorithm: 'none'
userinfo_signing_alg: 'none'
```
## See Also

View File

@ -61,7 +61,7 @@ To configure [Portainer] to utilize Authelia as an [OpenID Connect 1.0] Provider
### Authelia
The following YAML configuration is an example __Authelia__
[client configuration](../../../configuration/identity-providers/open-id-connect.md#clients) for use with [Portainer]
[client configuration](../../../configuration/identity-providers/openid-connect/clients.md) for use with [Portainer]
which will operate with the above example:
```yaml
@ -82,7 +82,7 @@ identity_providers:
- 'profile'
- 'groups'
- 'email'
userinfo_signing_algorithm: 'none'
userinfo_signing_alg: 'none'
```
## See Also

View File

@ -65,7 +65,7 @@ To configure [Proxmox] to utilize Authelia as an [OpenID Connect 1.0] Provider:
### Authelia
The following YAML configuration is an example __Authelia__
[client configuration](../../../configuration/identity-providers/open-id-connect.md#clients) for use with [Proxmox]
[client configuration](../../../configuration/identity-providers/openid-connect/clients.md) for use with [Proxmox]
which will operate with the above example:
```yaml
@ -85,7 +85,7 @@ identity_providers:
- 'openid'
- 'profile'
- 'email'
userinfo_signing_algorithm: 'none'
userinfo_signing_alg: 'none'
```
## See Also

View File

@ -69,7 +69,7 @@ OAUTH_ATTRIBUTE_MAP = {
### Authelia
The following YAML configuration is an example __Authelia__
[client configuration](../../../configuration/identity-providers/open-id-connect.md#clients) for use with [Seafile]
[client configuration](../../../configuration/identity-providers/openid-connect/clients.md) for use with [Seafile]
which will operate with the above example:
```yaml
@ -89,7 +89,7 @@ identity_providers:
- 'openid'
- 'profile'
- 'email'
userinfo_signing_algorithm: 'none'
userinfo_signing_alg: 'none'
```
## See Also

View File

@ -63,7 +63,7 @@ oidc_providers:
### Authelia
The following YAML configuration is an example __Authelia__
[client configuration](../../../configuration/identity-providers/open-id-connect.md#clients) for use with [Synapse]
[client configuration](../../../configuration/identity-providers/openid-connect/clients.md) for use with [Synapse]
which will operate with the above example:
```yaml
@ -83,7 +83,7 @@ identity_providers:
- 'openid'
- 'profile'
- 'email'
userinfo_signing_algorithm: 'none'
userinfo_signing_alg: 'none'
```
## See Also

View File

@ -65,7 +65,7 @@ To configure [Synology DSM] to utilize Authelia as an [OpenID Connect 1.0] Provi
### Authelia
The following YAML configuration is an example __Authelia__
[client configuration](../../../configuration/identity-providers/open-id-connect.md#clients) for use with [Synology DSM]
[client configuration](../../../configuration/identity-providers/openid-connect/clients.md) for use with [Synology DSM]
which will operate with the above example:
```yaml
@ -86,7 +86,7 @@ identity_providers:
- 'profile'
- 'groups'
- 'email'
userinfo_signing_algorithm: 'none'
userinfo_signing_alg: 'none'
```
## See Also

View File

@ -58,7 +58,7 @@ In addition this represents a bad user experience in some instances such as:
* Users sometimes visit the `https://app.example.com/authelia` URL which doesn't automatically redirect the user to
`https://app.example.com` (if they visit `https://app.example.com` then they'll be redirected to authenticate then
redirected back to their original URL)
* Administrators may wish to setup [OpenID Connect 1.0](../../configuration/identity-providers/open-id-connect.md) in
* Administrators may wish to setup [OpenID Connect 1.0](../../configuration/identity-providers/openid-connect/provider.md) in
which case it also doesn't represent a good user experience as the `issuer` will be
`https://app.example.com/authelia` for example
* Using the [SWAG] default configurations are more difficult to support as our specific familiarity is with our own

View File

@ -16,6 +16,6 @@ configure your applications to use Authelia as an [OpenID Connect 1.0 Provider](
currently operate as an [OpenID Connect 1.0 Relying Party](https://openid.net/connect/). This like all single-sign on
technologies requires support by the protected application.
See the [OpenID Connect 1.0 Configuration Guide](../../configuration/identity-providers/open-id-connect.md) and the
See the [OpenID Connect 1.0 Provider Configuration Guide](../../configuration/identity-providers/openid-connect/provider.md), and the
[OpenID Connect 1.0 Integration Guide](../../integration/openid-connect/introduction.md) for more information.

View File

@ -115,8 +115,15 @@ Feature List:
{{< roadmap-status stage="in-progress" version="v4.38.0" >}}
* [OAuth 2.0 Pushed Authorization Requests](https://datatracker.ietf.org/doc/html/rfc9126)
* [RFC9126: OAuth 2.0 Pushed Authorization Requests](https://datatracker.ietf.org/doc/html/rfc9126)
* [RFC7523: JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants](https://datatracker.ietf.org/doc/html/rfc7523):
* Client Auth Method `client_secret_jwt`
* Client Auth Method `private_key_jwt`
* Per-Client [Proof Key Code Exchange (PKCE)] Policy
* Multiple Issuer JWKs:
* RS256, RS384, RS512
* PS256, PS384, PS512
* ES256, ES384, ES512
### Beta 7

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
{{ $faq := "../frequently-asked-questions/" }}{{ $config := "../../../configuration/identity-providers/open-id-connect.md" }}
{{ $faq := "../frequently-asked-questions/" }}{{ $config := "../../../configuration/identity-providers/openid-connect/" }}
{{- with .Get "faq" }}{{ $faq = . }}{{ end }}
{{- with .Get "config" }}{{ $config = . }}{{ end }}
### Common Notes
@ -15,4 +15,6 @@
guaranteed to be supported in the future. See the [Plaintext]({{ $faq }}#plaintext) guide for more
information.
3. The Configuration example for Authelia is only a portion of the required configuration and it should be used as a
guide in conjunction with the standard [OpenID Connect 1.0 Configuration]({{ $config }}) guide.
guide in conjunction with the standard
[OpenID Connect 1.0 Provider Configuration]({{ printf "%s/provider.md" $config }}) and
[OpenID Connect 1.0 Clients Configuration]({{ printf "%s/clients.md" $config }}) guides.

View File

@ -4,7 +4,16 @@
# Authelia Configuration #
###############################################################################
## Note: the container by default expects to find this file at /config/configuration.yml.
##
## Notes:
##
## - the default location of this file is assumed to be configuration.yml unless otherwise noted
## - when using docker the container expects this by default to be at /config/configuration.yml
## - the default location where this file is loaded from can be overridden with the X_AUTHELIA_CONFIG environment var
## - the comments in this configuration file are helpful but users should consult the official documentation on the
## website at https://www.authelia.com/ or https://www.authelia.com/configuration/prologue/introduction/
## - this configuration file template is not automatically updated
##
## Certificates directory specifies where Authelia will load trusted certificates (public portion) from in addition to
## the system certificates store.
@ -357,73 +366,37 @@ authentication_backend:
## i.e. Mutual TLS.
# certificate_chain: |
# -----BEGIN CERTIFICATE-----
# MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
# EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
# MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
# ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q
# /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6
# LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY
# 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H
# kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR
# Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
# AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
# AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh
# /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4
# lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq
# wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg
# OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i
# ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE=
# MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT
# MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3
# NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
# AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00
# z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8
# Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx
# 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE=
# -----END CERTIFICATE-----
# -----BEGIN CERTIFICATE-----
# MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw
# EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
# MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
# ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S
# zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50
# 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou
# kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7
# ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi
# Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD
# AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
# Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/
# kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf
# 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ
# HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB
# D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj
# 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b
# qocikt3WAdU^invalid DO NOT USE=
# MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT
# MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3
# NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
# AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00
# z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8
# Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx
# 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE=
# -----END CERTIFICATE-----
## The private key used with the certificate_chain if the server requests TLS Client Authentication
## i.e. Mutual TLS.
# private_key: |
# -----BEGIN RSA PRIVATE KEY-----
# MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1
# T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ
# KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+
# +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW
# LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5
# txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um
# aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc
# Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB
# ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr
# LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG
# jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5
# BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR
# Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV
# R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i
# tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9
# ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR
# lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx
# 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos
# fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ
# 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX
# jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe
# rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84
# n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk
# yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ
# 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid..
# DO NOT USE==
# -----BEGIN RSA PRIVATE KEY-----
# MIIBPAIBAAJBAK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZF
# p7aTcToHMf00z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAQJBAJdpB0+RQ9ZFwy9Uk38P
# 5zZpUB8cL8ZFeEFluQeVbt0vyNa+cPLvDLouY87onduXtMz5AKIatLaTOjuG2thh
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
# -----END RSA PRIVATE KEY-----
## The distinguished name of the container searched for objects in the directory information tree.
@ -485,7 +458,7 @@ authentication_backend:
# permit_referrals: false
## The username and password of the admin user.
# user: cn=admin,dc=example,dc=com
# user: 'cn=admin,dc=example,dc=com'
## Password can also be set using a secret: https://www.authelia.com/c/secrets
# password: 'password'
@ -622,7 +595,7 @@ access_control:
# networks:
# - '10.10.0.0/16'
# - '192.168.2.0/24'
# - name: VPN
# - name: 'VPN'
# networks: '10.9.0.0/16'
# rules:
@ -748,7 +721,8 @@ session:
# expiration: '1h'
## The time before the cookie expires and the session is destroyed if remember me IS selected by the user. Setting
## this value to -1 disables remember me for this session cookie domain.
## this value to -1 disables remember me for this session cookie domain. If allowed and the user uses the remember
## me checkbox this overrides the expiration option and disables the inactivity option.
# remember_me: '1M'
## Cookie Session Domain default 'name' value.
@ -816,73 +790,37 @@ session:
## i.e. Mutual TLS.
# certificate_chain: |
# -----BEGIN CERTIFICATE-----
# MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
# EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
# MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
# ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q
# /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6
# LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY
# 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H
# kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR
# Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
# AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
# AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh
# /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4
# lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq
# wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg
# OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i
# ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE=
# MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT
# MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3
# NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
# AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00
# z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8
# Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx
# 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE=
# -----END CERTIFICATE-----
# -----BEGIN CERTIFICATE-----
# MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw
# EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
# MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
# ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S
# zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50
# 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou
# kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7
# ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi
# Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD
# AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
# Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/
# kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf
# 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ
# HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB
# D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj
# 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b
# qocikt3WAdU^invalid DO NOT USE=
# MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT
# MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3
# NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
# AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00
# z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8
# Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx
# 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE=
# -----END CERTIFICATE-----
## The private key used with the certificate_chain if the server requests TLS Client Authentication
## i.e. Mutual TLS.
# private_key: |
# -----BEGIN RSA PRIVATE KEY-----
# MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1
# T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ
# KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+
# +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW
# LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5
# txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um
# aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc
# Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB
# ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr
# LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG
# jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5
# BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR
# Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV
# R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i
# tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9
# ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR
# lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx
# 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos
# fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ
# 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX
# jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe
# rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84
# n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk
# yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ
# 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid..
# DO NOT USE==
# -----BEGIN RSA PRIVATE KEY-----
# MIIBPAIBAAJBAK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZF
# p7aTcToHMf00z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAQJBAJdpB0+RQ9ZFwy9Uk38P
# 5zZpUB8cL8ZFeEFluQeVbt0vyNa+cPLvDLouY87onduXtMz5AKIatLaTOjuG2thh
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
# -----END RSA PRIVATE KEY-----
## The Redis HA configuration options.
@ -997,73 +935,37 @@ regulation:
## i.e. Mutual TLS.
# certificate_chain: |
# -----BEGIN CERTIFICATE-----
# MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
# EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
# MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
# ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q
# /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6
# LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY
# 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H
# kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR
# Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
# AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
# AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh
# /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4
# lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq
# wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg
# OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i
# ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE=
# MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT
# MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3
# NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
# AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00
# z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8
# Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx
# 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE=
# -----END CERTIFICATE-----
# -----BEGIN CERTIFICATE-----
# MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw
# EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
# MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
# ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S
# zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50
# 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou
# kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7
# ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi
# Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD
# AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
# Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/
# kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf
# 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ
# HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB
# D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj
# 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b
# qocikt3WAdU^invalid DO NOT USE=
# MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT
# MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3
# NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
# AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00
# z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8
# Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx
# 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE=
# -----END CERTIFICATE-----
## The private key used with the certificate_chain if the server requests TLS Client Authentication
## i.e. Mutual TLS.
# private_key: |
# -----BEGIN RSA PRIVATE KEY-----
# MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1
# T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ
# KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+
# +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW
# LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5
# txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um
# aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc
# Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB
# ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr
# LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG
# jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5
# BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR
# Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV
# R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i
# tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9
# ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR
# lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx
# 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos
# fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ
# 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX
# jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe
# rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84
# n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk
# yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ
# 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid..
# DO NOT USE==
# -----BEGIN RSA PRIVATE KEY-----
# MIIBPAIBAAJBAK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZF
# p7aTcToHMf00z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAQJBAJdpB0+RQ9ZFwy9Uk38P
# 5zZpUB8cL8ZFeEFluQeVbt0vyNa+cPLvDLouY87onduXtMz5AKIatLaTOjuG2thh
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
# -----END RSA PRIVATE KEY-----
##
@ -1116,74 +1018,38 @@ regulation:
## i.e. Mutual TLS.
# certificate_chain: |
# -----BEGIN CERTIFICATE-----
# MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
# EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
# MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
# ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q
# /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6
# LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY
# 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H
# kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR
# Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
# AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
# AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh
# /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4
# lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq
# wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg
# OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i
# ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE=
# MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT
# MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3
# NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
# AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00
# z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8
# Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx
# 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE=
# -----END CERTIFICATE-----
# -----BEGIN CERTIFICATE-----
# MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw
# EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
# MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
# ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S
# zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50
# 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou
# kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7
# ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi
# Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD
# AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
# Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/
# kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf
# 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ
# HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB
# D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj
# 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b
# qocikt3WAdU^invalid DO NOT USE=
# MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT
# MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3
# NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
# AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00
# z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8
# Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx
# 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE=
# -----END CERTIFICATE-----
## The private key used with the certificate_chain if the server requests TLS Client Authentication
## i.e. Mutual TLS.
# private_key: |
# -----BEGIN RSA PRIVATE KEY-----
# MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1
# T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ
# KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+
# +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW
# LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5
# txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um
# aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc
# Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB
# ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr
# LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG
# jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5
# BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR
# Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV
# R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i
# tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9
# ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR
# lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx
# 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos
# fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ
# 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX
# jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe
# rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84
# n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk
# yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ
# 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid..
# DO NOT USE==
# -----END RSA PRIVATE KEY-----
# -----BEGIN RSA PRIVATE KEY-----
# MIIBPAIBAAJBAK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZF
# p7aTcToHMf00z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAQJBAJdpB0+RQ9ZFwy9Uk38P
# 5zZpUB8cL8ZFeEFluQeVbt0vyNa+cPLvDLouY87onduXtMz5AKIatLaTOjuG2thh
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
# -----END RSA PRIVATE KEY-----
##
## Notification Provider
@ -1270,73 +1136,37 @@ notifier:
## i.e. Mutual TLS.
# certificate_chain: |
# -----BEGIN CERTIFICATE-----
# MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
# EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
# MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
# ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q
# /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6
# LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY
# 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H
# kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR
# Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
# AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
# AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh
# /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4
# lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq
# wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg
# OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i
# ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE=
# MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT
# MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3
# NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
# AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00
# z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8
# Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx
# 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE=
# -----END CERTIFICATE-----
# -----BEGIN CERTIFICATE-----
# MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw
# EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
# MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
# ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S
# zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50
# 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou
# kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7
# ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi
# Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD
# AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
# Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/
# kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf
# 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ
# HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB
# D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj
# 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b
# qocikt3WAdU^invalid DO NOT USE=
# MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT
# MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3
# NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
# AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00
# z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8
# Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx
# 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE=
# -----END CERTIFICATE-----
## The private key used with the certificate_chain if the server requests TLS Client Authentication
## i.e. Mutual TLS.
# private_key: |
# -----BEGIN RSA PRIVATE KEY-----
# MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1
# T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ
# KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+
# +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW
# LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5
# txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um
# aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc
# Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB
# ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr
# LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG
# jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5
# BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR
# Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV
# R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i
# tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9
# ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR
# lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx
# 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos
# fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ
# 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX
# jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe
# rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84
# n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk
# yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ
# 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid..
# DO NOT USE==
# -----BEGIN RSA PRIVATE KEY-----
# MIIBPAIBAAJBAK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZF
# p7aTcToHMf00z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAQJBAJdpB0+RQ9ZFwy9Uk38P
# 5zZpUB8cL8ZFeEFluQeVbt0vyNa+cPLvDLouY87onduXtMz5AKIatLaTOjuG2thh
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
# -----END RSA PRIVATE KEY-----
##
@ -1354,80 +1184,88 @@ notifier:
## HMAC Secret can also be set using a secret: https://www.authelia.com/c/secrets
# hmac_secret: 'this_is_a_secret_abc123abc123abc'
## The issuer_certificate_chain is an optional PEM encoded certificate chain. It's used in conjunction with the
## issuer_private_key to sign JWT's. All certificates in the chain must be within the validity period, and every
## certificate included must be signed by the certificate immediately after it if provided.
# issuer_certificate_chain: |
# -----BEGIN CERTIFICATE-----
# MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
# EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
# MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
# ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q
# /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6
# LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY
# 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H
# kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR
# Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
# AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
# AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh
# /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4
# lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq
# wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg
# OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i
# ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE=
# -----END CERTIFICATE-----
# -----BEGIN CERTIFICATE-----
# MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw
# EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
# MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
# ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S
# zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50
# 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou
# kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7
# ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi
# Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD
# AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
# Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/
# kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf
# 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ
# HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB
# D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj
# 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b
# qocikt3WAdU^invalid DO NOT USE=
# -----END CERTIFICATE-----
## Issuer JWKS configures multiple JSON Web Keys. It's required that at least one of these is RS256 or the
## option issuer_private_key is configured. There must only be one key per algorithm at this time.
## For RSA keys the minimum is a 2048 bit key.
# issuer_private_keys:
# -
## Key ID embedded into the JWT header for key matching. Must be an alphanumeric string with 7 or less characters.
## This value is automatically generated if not provided. It's recommended to not configure this.
# key_id: 'example'
## The issuer_private_key is used to sign the JWT forged by OpenID Connect.
## The key algorithm used with this key.
# algorithm: 'RS256'
## The key use expected with this key. Currently only 'sig' is supported.
# use: 'sig'
## Required Private Key in PEM DER form.
# key: |
# -----BEGIN RSA PRIVATE KEY-----
# MIIBPAIBAAJBAK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZF
# p7aTcToHMf00z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAQJBAJdpB0+RQ9ZFwy9Uk38P
# 5zZpUB8cL8ZFeEFluQeVbt0vyNa+cPLvDLouY87onduXtMz5AKIatLaTOjuG2thh
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
# -----END RSA PRIVATE KEY-----
## Optional matching certificate chain in PEM DER form that matches the key. All certificates within the chain
## must be valid and current, and from top to bottom each certificate must be signed by the subsequent one.
# certificate_chain: |
# -----BEGIN CERTIFICATE-----
# MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT
# MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3
# NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
# AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00
# z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8
# Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx
# 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE=
# -----END CERTIFICATE-----
## The issuer_private_key is used to sign the JWT forged by OpenID Connect. This is in addition to the
## issuer_private_keys option. Assumed to use the RS256 algorithm, and must not be specified if any of the
## keys in issuer_private_keys also has the algorithm RS256 or are an RSA key without an algorithm.
## Issuer Private Key can also be set using a secret: https://www.authelia.com/c/secrets
# issuer_private_key: |
# -----BEGIN RSA PRIVATE KEY-----
# MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1
# T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ
# KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+
# +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW
# LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5
# txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um
# aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc
# Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB
# ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr
# LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG
# jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5
# BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR
# Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV
# R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i
# tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9
# ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR
# lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx
# 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos
# fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ
# 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX
# jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe
# rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84
# n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk
# yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ
# 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid..
# DO NOT USE==
# MIIBPAIBAAJBAK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZF
# p7aTcToHMf00z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAQJBAJdpB0+RQ9ZFwy9Uk38P
# 5zZpUB8cL8ZFeEFluQeVbt0vyNa+cPLvDLouY87onduXtMz5AKIatLaTOjuG2thh
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
# -----END RSA PRIVATE KEY-----
## Optional matching certificate chain in PEM DER form that matches the issuer_private_key. All certificates within
## the chain must be valid and current, and from top to bottom each certificate must be signed by the next
## certificate in the chain if provided.
# issuer_certificate_chain: |
# -----BEGIN CERTIFICATE-----
# MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT
# MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3
# NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
# AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00
# z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8
# Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx
# 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE=
# -----END CERTIFICATE-----
# -----BEGIN CERTIFICATE-----
# MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT
# MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3
# NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
# AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00
# z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8
# Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx
# 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE=
# -----END CERTIFICATE-----
## The lifespans configure the expiration for these token types in the duration common syntax.
# access_token_lifespan: '1h'
# authorize_code_lifespan: '1m'
@ -1499,6 +1337,11 @@ notifier:
# - 'email'
# - 'profile'
## Grant Types configures which grants this client can obtain.
## It's not recommended to define this unless you know what you're doing.
# grant_types:
# - 'authorization_code'
## Response Types configures which responses this client can be sent.
## It's not recommended to define this unless you know what you're doing.
# response_types:
@ -1509,25 +1352,19 @@ notifier:
# - 'form_post'
# - 'query'
## Grant Types configures which grants this client can obtain.
## It's not recommended to define this unless you know what you're doing.
# grant_types:
# - 'authorization_code'
## The permitted client authentication method for the Token Endpoint for this client.
# token_endpoint_auth_method: 'client_secret_basic'
## The permitted client authentication signing algorithm for the Token Endpoint for this client when using
## the 'client_secret_jwt' token_endpoint_auth_method.
# token_endpoint_auth_signing_alg: HS256
## The permitted client authentication signing algorithm for the Token Endpoint for this client when using
## the 'client_secret_jwt' token_endpoint_auth_method.
# token_endpoint_auth_signing_alg: HS256
## The policy to require for this client; one_factor or two_factor.
# authorization_policy: 'two_factor'
## The consent mode controls how consent is obtained.
# consent_mode: 'auto'
## This value controls the duration a consent on this client remains remembered when the consent mode is
## configured as 'auto' or 'pre-configured' in the duration common syntax.
# pre_configured_consent_duration: '1w'
## Enforces the use of Pushed Authorization Requests for this client when set to true.
# enforce_par: false
## Enforces the use of PKCE for this client when set to true.
# enforce_pkce: false
@ -1535,13 +1372,69 @@ notifier:
## Options are 'plain' and 'S256'.
# pkce_challenge_method: 'S256'
## The permitted client authentication method for the Token Endpoint for this client.
# token_endpoint_auth_method: 'client_secret_basic'
## The permitted client authentication signing algorithm for the Token Endpoint for this client when using
## the 'client_secret_jwt' or 'private_key_jwt' token_endpoint_auth_method.
# token_endpoint_auth_signing_alg: 'RS256'
## The signing algorithm which must be used for request objects. A client JWK with a matching algorithm must be
## included if configured.
# request_object_signing_alg: 'RS256'
## The signing algorithm used for ID Tokens. Am issuer JWK with a matching algorithm must be included.
# id_token_signing_alg: 'RS256'
## The algorithm used to sign userinfo endpoint responses for this client, either none or RS256.
# userinfo_signing_algorithm: 'none'
# userinfo_signing_alg: 'none'
## The consent mode controls how consent is obtained.
# consent_mode: 'auto'
## Trusted public keys configuration for request object signing for things such as private_key_jwt
# public_keys:
## This value controls the duration a consent on this client remains remembered when the consent mode is
## configured as 'auto' or 'pre-configured' in the duration common syntax.
# pre_configured_consent_duration: '1w'
## URL of the HTTPS endpoint which serves the keys. It's recommended to manually configure them in the
## values option below. Please note the URL and the individual values are mutually exclusive.
# uri: 'https://app.example.com/jwks.json'
## Values from the individual keys.
# values:
# -
## Key ID used to match the JWT's to an individual identifier. This option is required if configured.
# key_id: 'example'
## The key algorithm expected with this key.
# algorithm: 'RS256'
## The key use expected with this key. Currently only 'sig' is supported.
# use: 'sig'
## Required Public Key in PEM DER form.
# key: |
# -----BEGIN RSA PUBLIC KEY-----
# MEgCQQDAwV26ZA1lodtOQxNrJ491gWT+VzFum9IeZ+WTmMypYWyW1CzXKwsvTHDz
# 9ec+jserR3EMQ0Rr24lj13FL1ib5AgMBAAE=
# -----END RSA PUBLIC KEY----
## The matching certificate chain in PEM DER form that matches the key if available.
# certificate_chain: |
# -----BEGIN CERTIFICATE-----
# MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT
# MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3
# NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
# AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00
# z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8
# Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx
# 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE=
# -----END CERTIFICATE-----
# -----BEGIN CERTIFICATE-----
# MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT
# MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3
# NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
# AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00
# z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8
# Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx
# 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE=
# -----END CERTIFICATE-----
...

View File

@ -253,4 +253,12 @@ var deprecations = map[string]Deprecation{
MapFunc: nil,
ErrFunc: nil,
},
"identity_providers.oidc.clients[].userinfo_signing_algorithm": {
Version: model.SemanticVersion{Major: 4, Minor: 38},
Key: "identity_providers.oidc.clients[].userinfo_signing_algorithm",
NewKey: "identity_providers.oidc.clients[].userinfo_signing_alg",
AutoMap: true,
MapFunc: nil,
ErrFunc: nil,
},
}

View File

@ -245,37 +245,6 @@ func TestShouldLoadURLList(t *testing.T) {
assert.Equal(t, "https://example.com", config.IdentityProviders.OIDC.CORS.AllowedOrigins[1].String())
}
/*
func TestShouldLoadNewOIDCConfig(t *testing.T) {
val := schema.NewStructValidator()
_, config, err := Load(val, NewDefaultSources([]string{"./test_resources/config_oidc_modern.yml"}, DefaultEnvPrefix, DefaultEnvDelimiter)...)
assert.NoError(t, err)
assert.Len(t, val.Errors(), 0)
assert.Len(t, val.Warnings(), 0)
val.Clear()
validator.ValidateIdentityProviders(&config.IdentityProviders, val)
assert.Len(t, val.Errors(), 0)
assert.Len(t, config.IdentityProviders.OIDC.IssuerJWKS.Keys, 2)
assert.Equal(t, "keya", config.IdentityProviders.OIDC.IssuerJWKS.DefaultKeyID)
assert.Equal(t, oidc.KeyUseSignature, config.IdentityProviders.OIDC.IssuerJWKS.Keys["keya"].Use)
assert.Equal(t, oidc.SigningAlgRSAUsingSHA256, config.IdentityProviders.OIDC.IssuerJWKS.Keys["keya"].Algorithm)
assert.Equal(t, oidc.KeyUseSignature, config.IdentityProviders.OIDC.IssuerJWKS.Keys["ec521"].Use)
assert.Equal(t, oidc.SigningAlgECDSAUsingP521AndSHA512, config.IdentityProviders.OIDC.IssuerJWKS.Keys["ec521"].Algorithm)
assert.Contains(t, config.IdentityProviders.OIDC.Discovery.RegisteredJWKSigningAlgs, oidc.SigningAlgRSAUsingSHA256)
assert.Contains(t, config.IdentityProviders.OIDC.Discovery.RegisteredJWKSigningAlgs, oidc.SigningAlgECDSAUsingP521AndSHA512)
}.
*/
func TestShouldConfigureConsent(t *testing.T) {
val := schema.NewStructValidator()
keys, config, err := Load(val, NewDefaultSources([]string{"./test_resources/config_oidc.yml"}, DefaultEnvPrefix, DefaultEnvDelimiter)...)
@ -289,6 +258,7 @@ func TestShouldConfigureConsent(t *testing.T) {
require.Len(t, config.IdentityProviders.OIDC.Clients, 1)
assert.Equal(t, config.IdentityProviders.OIDC.Clients[0].ConsentMode, "explicit")
assert.Equal(t, "none", config.IdentityProviders.OIDC.Clients[0].UserinfoSigningAlg)
}
func TestShouldValidateAndRaiseErrorsOnBadConfiguration(t *testing.T) {

View File

@ -13,12 +13,12 @@ type IdentityProvidersConfiguration struct {
// OpenIDConnectConfiguration configuration for OpenID Connect.
type OpenIDConnectConfiguration struct {
HMACSecret string `koanf:"hmac_secret"`
HMACSecret string `koanf:"hmac_secret"`
IssuerPrivateKeys []JWK `koanf:"issuer_private_keys"`
IssuerCertificateChain X509CertificateChain `koanf:"issuer_certificate_chain"`
IssuerPrivateKey *rsa.PrivateKey `koanf:"issuer_private_key"`
IssuerJWKS []JWK `koanf:"issuer_jwks"`
AccessTokenLifespan time.Duration `koanf:"access_token_lifespan"`
AuthorizeCodeLifespan time.Duration `koanf:"authorize_code_lifespan"`
IDTokenLifespan time.Duration `koanf:"id_token_lifespan"`
@ -30,8 +30,8 @@ type OpenIDConnectConfiguration struct {
EnforcePKCE string `koanf:"enforce_pkce"`
EnablePKCEPlainChallenge bool `koanf:"enable_pkce_plain_challenge"`
CORS OpenIDConnectCORSConfiguration `koanf:"cors"`
PAR OpenIDConnectPARConfiguration `koanf:"pushed_authorizations"`
CORS OpenIDConnectCORSConfiguration `koanf:"cors"`
Clients []OpenIDConnectClientConfiguration `koanf:"clients"`
@ -39,8 +39,9 @@ type OpenIDConnectConfiguration struct {
}
type OpenIDConnectDiscovery struct {
DefaultKeyID string
RegisteredJWKSigningAlgs []string
DefaultKeyID string
ResponseObjectSigningAlgs []string
RequestObjectSigningAlgs []string
}
// OpenIDConnectPARConfiguration represents an OpenID Connect PAR config.
@ -73,21 +74,31 @@ type OpenIDConnectClientConfiguration struct {
ResponseTypes []string `koanf:"response_types"`
ResponseModes []string `koanf:"response_modes"`
TokenEndpointAuthMethod string `koanf:"token_endpoint_auth_method"`
TokenEndpointAuthSigningAlg string `koanf:"token_endpoint_auth_signing_alg"`
IDTokenSigningAlg string `koanf:"id_token_signing_alg"`
Policy string `koanf:"authorization_policy"`
ConsentMode string `koanf:"consent_mode"`
ConsentPreConfiguredDuration *time.Duration `koanf:"pre_configured_consent_duration"`
EnforcePAR bool `koanf:"enforce_par"`
EnforcePKCE bool `koanf:"enforce_pkce"`
PKCEChallengeMethod string `koanf:"pkce_challenge_method"`
UserinfoSigningAlg string `koanf:"userinfo_signing_algorithm"`
ConsentMode string `koanf:"consent_mode"`
ConsentPreConfiguredDuration *time.Duration `koanf:"pre_configured_consent_duration"`
TokenEndpointAuthMethod string `koanf:"token_endpoint_auth_method"`
TokenEndpointAuthSigningAlg string `koanf:"token_endpoint_auth_signing_alg"`
RequestObjectSigningAlg string `koanf:"request_object_signing_alg"`
IDTokenSigningAlg string `koanf:"id_token_signing_alg"`
UserinfoSigningAlg string `koanf:"userinfo_signing_alg"`
PublicKeys OpenIDConnectClientPublicKeys `koanf:"public_keys"`
Discovery OpenIDConnectDiscovery
}
type OpenIDConnectClientPublicKeys struct {
URI *url.URL `koanf:"uri"`
Values []JWK `koanf:"values"`
}
// DefaultOpenIDConnectConfiguration contains defaults for OIDC.

View File

@ -18,14 +18,14 @@ var Keys = []string{
"log.file_path",
"log.keep_stdout",
"identity_providers.oidc.hmac_secret",
"identity_providers.oidc.issuer_private_keys",
"identity_providers.oidc.issuer_private_keys[].key_id",
"identity_providers.oidc.issuer_private_keys[]",
"identity_providers.oidc.issuer_private_keys[].algorithm",
"identity_providers.oidc.issuer_private_keys[].key",
"identity_providers.oidc.issuer_private_keys[].certificate_chain",
"identity_providers.oidc.issuer_certificate_chain",
"identity_providers.oidc.issuer_private_key",
"identity_providers.oidc.issuer_jwks",
"identity_providers.oidc.issuer_jwks[].key_id",
"identity_providers.oidc.issuer_jwks[]",
"identity_providers.oidc.issuer_jwks[].algorithm",
"identity_providers.oidc.issuer_jwks[].key",
"identity_providers.oidc.issuer_jwks[].certificate_chain",
"identity_providers.oidc.access_token_lifespan",
"identity_providers.oidc.authorize_code_lifespan",
"identity_providers.oidc.id_token_lifespan",
@ -34,11 +34,11 @@ var Keys = []string{
"identity_providers.oidc.minimum_parameter_entropy",
"identity_providers.oidc.enforce_pkce",
"identity_providers.oidc.enable_pkce_plain_challenge",
"identity_providers.oidc.pushed_authorizations.enforce",
"identity_providers.oidc.pushed_authorizations.context_lifespan",
"identity_providers.oidc.cors.endpoints",
"identity_providers.oidc.cors.allowed_origins",
"identity_providers.oidc.cors.allowed_origins_from_client_redirect_uris",
"identity_providers.oidc.pushed_authorizations.enforce",
"identity_providers.oidc.pushed_authorizations.context_lifespan",
"identity_providers.oidc.clients",
"identity_providers.oidc.clients[].id",
"identity_providers.oidc.clients[].description",
@ -51,16 +51,25 @@ var Keys = []string{
"identity_providers.oidc.clients[].grant_types",
"identity_providers.oidc.clients[].response_types",
"identity_providers.oidc.clients[].response_modes",
"identity_providers.oidc.clients[].token_endpoint_auth_method",
"identity_providers.oidc.clients[].token_endpoint_auth_signing_alg",
"identity_providers.oidc.clients[].id_token_signing_alg",
"identity_providers.oidc.clients[].authorization_policy",
"identity_providers.oidc.clients[].consent_mode",
"identity_providers.oidc.clients[].pre_configured_consent_duration",
"identity_providers.oidc.clients[].enforce_par",
"identity_providers.oidc.clients[].enforce_pkce",
"identity_providers.oidc.clients[].pkce_challenge_method",
"identity_providers.oidc.clients[].userinfo_signing_algorithm",
"identity_providers.oidc.clients[].consent_mode",
"identity_providers.oidc.clients[].pre_configured_consent_duration",
"identity_providers.oidc.clients[].token_endpoint_auth_method",
"identity_providers.oidc.clients[].token_endpoint_auth_signing_alg",
"identity_providers.oidc.clients[].request_object_signing_alg",
"identity_providers.oidc.clients[].id_token_signing_alg",
"identity_providers.oidc.clients[].userinfo_signing_alg",
"identity_providers.oidc.clients[].public_keys.uri",
"identity_providers.oidc.clients[].public_keys.values",
"identity_providers.oidc.clients[].public_keys.values[].key_id",
"identity_providers.oidc.clients[].public_keys.values[]",
"identity_providers.oidc.clients[].public_keys.values[].algorithm",
"identity_providers.oidc.clients[].public_keys.values[].key",
"identity_providers.oidc.clients[].public_keys.values[].certificate_chain",
"identity_providers.oidc.clients[]",
"identity_providers.oidc",
"authentication_backend.password_reset.disable",
"authentication_backend.password_reset.custom_url",

View File

@ -102,6 +102,7 @@ func NewX509CertificateChain(in string) (chain *X509CertificateChain, err error)
return chain, nil
}
// NewX509CertificateChainFromCerts returns a chain from a given list of certificates without validation.
func NewX509CertificateChainFromCerts(in []*x509.Certificate) (chain X509CertificateChain) {
return X509CertificateChain{certs: in}
}

View File

@ -218,6 +218,11 @@ func TestNewX509CertificateChain(t *testing.T) {
}
}
func TestNewX509CertificateChainFromCerts(t *testing.T) {
have := NewX509CertificateChainFromCerts(nil)
assert.NotNil(t, have)
}
func TestX509CertificateChain(t *testing.T) {
chain := &X509CertificateChain{}

View File

@ -131,4 +131,5 @@ identity_providers:
- id: 'abc'
secret: '123'
consent_mode: 'explicit'
userinfo_signing_alg: 'none'
...

View File

@ -127,7 +127,7 @@ notifier:
identity_providers:
oidc:
hmac_secret: 1nb2j3kh1b23kjh1b23jh1b23j1h2b3
issuer_jwks:
issuer_private_keys:
keys:
keya:
key: |

View File

@ -142,15 +142,25 @@ const (
// OpenID Error constants.
const (
errFmtOIDCNoClientsConfigured = "identity_providers: oidc: option 'clients' must have one or " +
errFmtOIDCProviderNoClientsConfigured = "identity_providers: oidc: option 'clients' must have one or " +
"more clients configured"
errFmtOIDCNoPrivateKey = "identity_providers: oidc: option 'issuer_private_key' or `issuer_jwks` is required"
errFmtOIDCInvalidPrivateKeyBitSize = "identity_providers: oidc: option 'issuer_private_key' must be an RSA private key with %d bits or more but it only has %d bits"
errFmtOIDCInvalidPrivateKeyMalformedMissingPublicKey = "identity_providers: oidc: option 'issuer_private_key' must be a valid RSA private key but the provided data is missing the public key bits"
errFmtOIDCCertificateMismatch = "identity_providers: oidc: option 'issuer_private_key' does not appear to be the private key the certificate provided by option 'issuer_certificate_chain'"
errFmtOIDCCertificateChain = "identity_providers: oidc: option 'issuer_certificate_chain' produced an error during validation of the chain: %w"
errFmtOIDCEnforcePKCEInvalidValue = "identity_providers: oidc: option 'enforce_pkce' must be 'never', " +
errFmtOIDCProviderNoPrivateKey = "identity_providers: oidc: option `issuer_private_keys` or 'issuer_private_key' is required"
errFmtOIDCProviderEnforcePKCEInvalidValue = "identity_providers: oidc: option 'enforce_pkce' must be 'never', " +
"'public_clients_only' or 'always', but it's configured as '%s'"
errFmtOIDCProviderInsecureParameterEntropy = "openid connect provider: SECURITY ISSUE - minimum parameter entropy is " +
"configured to an unsafe value, it should be above 8 but it's configured to %d"
errFmtOIDCProviderPrivateKeysInvalid = "identity_providers: oidc: issuer_private_keys: key #%d: option 'key' must be a valid private key but the provided data is malformed as it's missing the public key bits"
errFmtOIDCProviderPrivateKeysCalcThumbprint = "identity_providers: oidc: issuer_private_keys: key #%d: option 'key' failed to calculate thumbprint to configure key id value: %w"
errFmtOIDCProviderPrivateKeysKeyIDLength = "identity_providers: oidc: issuer_private_keys: key #%d with key id '%s': option `key_id`` must be 7 characters or less"
errFmtOIDCProviderPrivateKeysAttributeNotUnique = "identity_providers: oidc: issuer_private_keys: key #%d with key id '%s': option '%s' must be unique"
errFmtOIDCProviderPrivateKeysKeyIDNotAlphaNumeric = "identity_providers: oidc: issuer_private_keys: key #%d with key id '%s': option 'key_id' must only have alphanumeric characters"
errFmtOIDCProviderPrivateKeysProperties = "identity_providers: oidc: issuer_private_keys: key #%d with key id '%s': option 'key' failed to get key properties: %w"
errFmtOIDCProviderPrivateKeysInvalidOptionOneOf = "identity_providers: oidc: issuer_private_keys: key #%d with key id '%s': option '%s' must be one of %s but it's configured as '%s'"
errFmtOIDCProviderPrivateKeysRSAKeyLessThan2048Bits = "identity_providers: oidc: issuer_private_keys: key #%d with key id '%s': option 'key' is an RSA %d bit private key but it must at minimum be a RSA 2048 bit private key"
errFmtOIDCProviderPrivateKeysKeyNotRSAOrECDSA = "identity_providers: oidc: issuer_private_keys: key #%d with key id '%s': option 'key' must be a RSA private key or ECDSA private key but it's type is %T"
errFmtOIDCProviderPrivateKeysKeyCertificateMismatch = "identity_providers: oidc: issuer_private_keys: key #%d with key id '%s': option 'certificate_chain' does not appear to contain the public key for the private key provided by option 'key'"
errFmtOIDCProviderPrivateKeysCertificateChainInvalid = "identity_providers: oidc: issuer_private_keys: key #%d with key id '%s': option 'certificate_chain' produced an error during validation of the chain: %w"
errFmtOIDCProviderPrivateKeysNoRS256 = "identity_providers: oidc: issuer_private_keys: keys: must at least have one key supporting the '%s' algorithm but only has %s"
errFmtOIDCCORSInvalidOrigin = "identity_providers: oidc: cors: option 'allowed_origins' contains an invalid value '%s' as it has a %s: origins must only be scheme, hostname, and an optional port"
errFmtOIDCCORSInvalidOriginWildcard = "identity_providers: oidc: cors: option 'allowed_origins' contains the wildcard origin '*' with more than one origin but the wildcard origin must be defined by itself"
@ -161,45 +171,61 @@ const (
errFmtOIDCClientsWithEmptyID = "identity_providers: oidc: clients: option 'id' is required but was absent on the clients in positions %s"
errFmtOIDCClientsDeprecated = "identity_providers: oidc: clients: warnings for clients above indicate deprecated functionality and it's strongly suggested these issues are checked and fixed if they're legitimate issues or reported if they are not as in a future version these warnings will become errors"
errFmtOIDCClientInvalidSecret = "identity_providers: oidc: client '%s': option 'secret' is required"
errFmtOIDCClientInvalidSecretPlainText = "identity_providers: oidc: client '%s': option 'secret' is plaintext but for clients not using the 'token_endpoint_auth_method' of 'client_secret_jwt' it should be a hashed value as plaintext values are deprecated with the exception of 'client_secret_jwt' and will be removed when oidc becomes stable"
errFmtOIDCClientInvalidSecretNotPlainText = "identity_providers: oidc: client '%s': option 'secret' must be plaintext with option 'token_endpoint_auth_method' with a value of 'client_secret_jwt'"
errFmtOIDCClientPublicInvalidSecret = "identity_providers: oidc: client '%s': option 'secret' is " +
errFmtOIDCClientInvalidSecret = "identity_providers: oidc: clients: client '%s': option 'secret' is required"
errFmtOIDCClientInvalidSecretPlainText = "identity_providers: oidc: clients: client '%s': option 'secret' is plaintext but for clients not using the 'token_endpoint_auth_method' of 'client_secret_jwt' it should be a hashed value as plaintext values are deprecated with the exception of 'client_secret_jwt' and will be removed when oidc becomes stable"
errFmtOIDCClientInvalidSecretNotPlainText = "identity_providers: oidc: clients: client '%s': option 'secret' must be plaintext with option 'token_endpoint_auth_method' with a value of 'client_secret_jwt'"
errFmtOIDCClientPublicInvalidSecret = "identity_providers: oidc: clients: client '%s': option 'secret' is " +
"required to be empty when option 'public' is true"
errFmtOIDCClientRedirectURICantBeParsed = "identity_providers: oidc: client '%s': option 'redirect_uris' has an " +
errFmtOIDCClientRedirectURICantBeParsed = "identity_providers: oidc: clients: client '%s': option 'redirect_uris' has an " +
"invalid value: redirect uri '%s' could not be parsed: %v"
errFmtOIDCClientRedirectURIPublic = "identity_providers: oidc: client '%s': option 'redirect_uris' has the " +
errFmtOIDCClientRedirectURIPublic = "identity_providers: oidc: clients: client '%s': option 'redirect_uris' has the " +
"redirect uri '%s' when option 'public' is false but this is invalid as this uri is not valid " +
"for the openid connect confidential client type"
errFmtOIDCClientRedirectURIAbsolute = "identity_providers: oidc: client '%s': option 'redirect_uris' has an " +
errFmtOIDCClientRedirectURIAbsolute = "identity_providers: oidc: clients: client '%s': option 'redirect_uris' has an " +
"invalid value: redirect uri '%s' must have a scheme but it's absent"
errFmtOIDCClientInvalidConsentMode = "identity_providers: oidc: client '%s': consent: option 'mode' must be one of " +
errFmtOIDCClientInvalidConsentMode = "identity_providers: oidc: clients: client '%s': consent: option 'mode' must be one of " +
"%s but it's configured as '%s'"
errFmtOIDCClientInvalidEntries = "identity_providers: oidc: client '%s': option '%s' must only have the values " +
errFmtOIDCClientInvalidEntries = "identity_providers: oidc: clients: client '%s': option '%s' must only have the values " +
"%s but the values %s are present"
errFmtOIDCClientInvalidEntryDuplicates = "identity_providers: oidc: client '%s': option '%s' must have unique values but the values %s are duplicated"
errFmtOIDCClientInvalidValue = "identity_providers: oidc: client '%s': option " +
errFmtOIDCClientInvalidEntryDuplicates = "identity_providers: oidc: clients: client '%s': option '%s' must have unique values but the values %s are duplicated"
errFmtOIDCClientInvalidValue = "identity_providers: oidc: clients: client '%s': option " +
"'%s' must be one of %s but it's configured as '%s'"
errFmtOIDCClientInvalidTokenEndpointAuthMethod = "identity_providers: oidc: client '%s': option " +
errFmtOIDCClientInvalidTokenEndpointAuthMethod = "identity_providers: oidc: clients: client '%s': option " +
"'token_endpoint_auth_method' must be one of %s when configured as the confidential client type unless it only includes implicit flow response types such as %s but it's configured as '%s'"
errFmtOIDCClientInvalidTokenEndpointAuthMethodPublic = "identity_providers: oidc: client '%s': option " +
errFmtOIDCClientInvalidTokenEndpointAuthMethodPublic = "identity_providers: oidc: clients: client '%s': option " +
"'token_endpoint_auth_method' must be 'none' when configured as the public client type but it's configured as '%s'"
errFmtOIDCClientInvalidTokenEndpointAuthSigAlg = "identity_providers: oidc: client '%s': option " +
"'token_endpoint_auth_signing_alg' must be %s when option 'token_endpoint_auth_method' is %s"
errFmtOIDCClientInvalidSectorIdentifier = "identity_providers: oidc: client '%s': option " +
errFmtOIDCClientInvalidTokenEndpointAuthSigAlg = "identity_providers: oidc: clients: client '%s': option " +
"'token_endpoint_auth_signing_alg' must be one of %s when option 'token_endpoint_auth_method' is configured to '%s'"
errFmtOIDCClientInvalidTokenEndpointAuthSigAlgReg = "identity_providers: oidc: clients: client '%s': option " +
"'token_endpoint_auth_signing_alg' must be one of registered public key algorithm values %s when option 'token_endpoint_auth_method' is configured to '%s'"
errFmtOIDCClientInvalidTokenEndpointAuthSigAlgMissingPrivateKeyJWT = "identity_providers: oidc: clients: client '%s': option " +
"'token_endpoint_auth_signing_alg' is required when option 'token_endpoint_auth_method' is configured to 'private_key_jwt'"
errFmtOIDCClientInvalidPublicKeysPrivateKeyJWT = "identity_providers: oidc: clients: client '%s': option " +
"'public_keys' is required with 'token_endpoint_auth_method' set to 'private_key_jwt'"
errFmtOIDCClientInvalidSectorIdentifier = "identity_providers: oidc: clients: client '%s': option " +
"'sector_identifier' with value '%s': must be a URL with only the host component for example '%s' but it has a %s with the value '%s'"
errFmtOIDCClientInvalidSectorIdentifierWithoutValue = "identity_providers: oidc: client '%s': option " +
errFmtOIDCClientInvalidSectorIdentifierWithoutValue = "identity_providers: oidc: clients: client '%s': option " +
"'sector_identifier' with value '%s': must be a URL with only the host component for example '%s' but it has a %s"
errFmtOIDCClientInvalidSectorIdentifierHost = "identity_providers: oidc: client '%s': option " +
errFmtOIDCClientInvalidSectorIdentifierHost = "identity_providers: oidc: clients: client '%s': option " +
"'sector_identifier' with value '%s': must be a URL with only the host component but appears to be invalid"
errFmtOIDCClientInvalidGrantTypeMatch = "identity_providers: oidc: client '%s': option " +
errFmtOIDCClientInvalidGrantTypeMatch = "identity_providers: oidc: clients: client '%s': option " +
"'grant_types' should only have grant type values which are valid with the configured 'response_types' for the client but '%s' expects a response type %s such as %s but the response types are %s"
errFmtOIDCClientInvalidGrantTypeRefresh = "identity_providers: oidc: client '%s': option " +
errFmtOIDCClientInvalidGrantTypeRefresh = "identity_providers: oidc: clients: client '%s': option " +
"'grant_types' should only have the 'refresh_token' value if the client is also configured with the 'offline_access' scope"
errFmtOIDCClientInvalidRefreshTokenOptionWithoutCodeResponseType = "identity_providers: oidc: client '%s': option " +
errFmtOIDCClientInvalidRefreshTokenOptionWithoutCodeResponseType = "identity_providers: oidc: clients: client '%s': option " +
"'%s' should only have the values %s if the client is also configured with a 'response_type' such as %s which respond with authorization codes"
errFmtOIDCServerInsecureParameterEntropy = "openid connect provider: SECURITY ISSUE - minimum parameter entropy is " +
"configured to an unsafe value, it should be above 8 but it's configured to %d"
errFmtOIDCClientPublicKeysBothURIAndValuesConfigured = "identity_providers: oidc: clients: client '%s': public_keys: option 'uri' must not be defined at the same time as option 'values'"
errFmtOIDCClientPublicKeysURIInvalidScheme = "identity_providers: oidc: clients: client '%s': public_keys: option 'uri' must have the 'https' scheme but the scheme is '%s'"
errFmtOIDCClientPublicKeysProperties = "identity_providers: oidc: clients: client '%s': public_keys: values: key #%d with key id '%s': option 'key' failed to get key properties: %w"
errFmtOIDCClientPublicKeysInvalidOptionOneOf = "identity_providers: oidc: clients: client '%s': public_keys: values: key #%d with key id '%s': option '%s' must be one of %s but it's configured as '%s'"
errFmtOIDCClientPublicKeysInvalidOptionMissingOneOf = "identity_providers: oidc: clients: client '%s': public_keys: values: key #%d: option '%s' must be provided"
errFmtOIDCClientPublicKeysKeyMalformed = "identity_providers: oidc: clients: client '%s': public_keys: values: key #%d: option 'key' option 'key' must be a valid private key but the provided data is malformed as it's missing the public key bits"
errFmtOIDCClientPublicKeysRSAKeyLessThan2048Bits = "identity_providers: oidc: clients: client '%s': public_keys: values: key #%d with key id '%s': option 'key' is an RSA %d bit private key but it must at minimum be a RSA 2048 bit private key"
errFmtOIDCClientPublicKeysKeyNotRSAOrECDSA = "identity_providers: oidc: clients: client '%s': public_keys: values: key #%d with key id '%s': option 'key' must be a RSA public key or ECDSA public key but it's type is %T"
errFmtOIDCClientPublicKeysCertificateChainKeyMismatch = "identity_providers: oidc: clients: client '%s': public_keys: values: key #%d with key id '%s': option 'certificate_chain' does not appear to contain the public key for the public key provided by option 'key'"
errFmtOIDCClientPublicKeysCertificateChainInvalid = "identity_providers: oidc: clients: client '%s': public_keys: values: key #%d with key id '%s': option 'certificate_chain' produced an error during validation of the chain: %w"
errFmtOIDCClientPublicKeysROSAMissingAlgorithm = "identity_providers: oidc: clients: client '%s': option 'request_object_signing_alg' must be one of %s configured in the client option 'public_keys'"
)
// WebAuthn Error constants.
@ -288,11 +314,8 @@ const (
// Server Error constants.
const (
errFmtServerTLSCert = "server: tls: option 'key' must also be accompanied by option 'certificate'"
errFmtServerTLSKey = "server: tls: option 'certificate' must also be accompanied by option 'key'"
errFmtServerTLSFileNotExist = "server: tls: option '%s' the file '%s' does not exist"
errFmtServerTLSFileNotExistErr = "server: tls: option '%s' could not determine if the file '%s' exists: %w"
errFmtServerTLSCert = "server: tls: option 'key' must also be accompanied by option 'certificate'"
errFmtServerTLSKey = "server: tls: option 'certificate' must also be accompanied by option 'key'"
errFmtServerTLSClientAuthNoAuth = "server: tls: client authentication cannot be configured if no server certificate and key are provided"
errFmtServerAddressLegacyAndModern = "server: option 'host' and 'port' can't be configured at the same time as 'address'"
@ -402,13 +425,17 @@ var (
var validDefault2FAMethods = []string{"totp", "webauthn", "mobile_push"}
const (
attrOIDCKey = "key"
attrOIDCKeyID = "key_id"
attrOIDCKeyUse = "use"
attrOIDCAlgorithm = "algorithm"
attrOIDCScopes = "scopes"
attrOIDCResponseTypes = "response_types"
attrOIDCResponseModes = "response_modes"
attrOIDCGrantTypes = "grant_types"
attrOIDCRedirectURIs = "redirect_uris"
attrOIDCTokenAuthMethod = "token_endpoint_auth_method"
attrOIDCUsrSigAlg = "userinfo_signing_algorithm"
attrOIDCUsrSigAlg = "userinfo_signing_alg"
attrOIDCIDTokenSigAlg = "id_token_signing_alg"
attrOIDCPKCEChallengeMethod = "pkce_challenge_method"
)
@ -425,10 +452,10 @@ var (
validOIDCClientResponseTypesRefreshToken = []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeHybridFlowIDToken, oidc.ResponseTypeHybridFlowToken, oidc.ResponseTypeHybridFlowBoth}
validOIDCClientGrantTypes = []string{oidc.GrantTypeImplicit, oidc.GrantTypeRefreshToken, oidc.GrantTypeAuthorizationCode}
validOIDCClientTokenEndpointAuthMethods = []string{oidc.ClientAuthMethodNone, oidc.ClientAuthMethodClientSecretPost, oidc.ClientAuthMethodClientSecretBasic, oidc.ClientAuthMethodClientSecretJWT}
validOIDCClientTokenEndpointAuthMethodsConfidential = []string{oidc.ClientAuthMethodClientSecretPost, oidc.ClientAuthMethodClientSecretBasic}
validOIDCClientTokenEndpointAuthSigAlgs = []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512}
validOIDCIssuerJWKSigningAlgs = []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgRSAPSSUsingSHA256, oidc.SigningAlgECDSAUsingP256AndSHA256, oidc.SigningAlgRSAUsingSHA384, oidc.SigningAlgRSAPSSUsingSHA384, oidc.SigningAlgECDSAUsingP384AndSHA384, oidc.SigningAlgRSAUsingSHA512, oidc.SigningAlgRSAPSSUsingSHA512, oidc.SigningAlgECDSAUsingP521AndSHA512}
validOIDCClientTokenEndpointAuthMethods = []string{oidc.ClientAuthMethodNone, oidc.ClientAuthMethodClientSecretPost, oidc.ClientAuthMethodClientSecretBasic, oidc.ClientAuthMethodPrivateKeyJWT, oidc.ClientAuthMethodClientSecretJWT}
validOIDCClientTokenEndpointAuthMethodsConfidential = []string{oidc.ClientAuthMethodClientSecretPost, oidc.ClientAuthMethodClientSecretBasic, oidc.ClientAuthMethodPrivateKeyJWT}
validOIDCClientTokenEndpointAuthSigAlgsClientSecretJWT = []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512}
validOIDCIssuerJWKSigningAlgs = []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgRSAPSSUsingSHA256, oidc.SigningAlgECDSAUsingP256AndSHA256, oidc.SigningAlgRSAUsingSHA384, oidc.SigningAlgRSAPSSUsingSHA384, oidc.SigningAlgECDSAUsingP384AndSHA384, oidc.SigningAlgRSAUsingSHA512, oidc.SigningAlgRSAPSSUsingSHA512, oidc.SigningAlgECDSAUsingP521AndSHA512}
)
var (

View File

@ -17,6 +17,7 @@ const (
const (
exampleDotCom = "example.com"
rs256 = "rs256"
)
const (

View File

@ -12,7 +12,7 @@ import (
"strings"
"time"
"gopkg.in/square/go-jose.v2"
jose "gopkg.in/square/go-jose.v2"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/oidc"
@ -33,20 +33,23 @@ func validateOIDC(config *schema.OpenIDConnectConfiguration, val *schema.StructV
validateOIDCIssuer(config, val)
sort.Sort(oidc.SortedSigningAlgs(config.Discovery.RegisteredJWKSigningAlgs))
sort.Sort(oidc.SortedSigningAlgs(config.Discovery.ResponseObjectSigningAlgs))
if config.MinimumParameterEntropy != 0 && config.MinimumParameterEntropy < 8 {
val.PushWarning(fmt.Errorf(errFmtOIDCServerInsecureParameterEntropy, config.MinimumParameterEntropy))
val.PushWarning(fmt.Errorf(errFmtOIDCProviderInsecureParameterEntropy, config.MinimumParameterEntropy))
}
if config.EnforcePKCE != "never" && config.EnforcePKCE != "public_clients_only" && config.EnforcePKCE != "always" {
val.Push(fmt.Errorf(errFmtOIDCEnforcePKCEInvalidValue, config.EnforcePKCE))
switch config.EnforcePKCE {
case "always", "never", "public_clients_only":
break
default:
val.Push(fmt.Errorf(errFmtOIDCProviderEnforcePKCEInvalidValue, config.EnforcePKCE))
}
validateOIDCOptionsCORS(config, val)
if len(config.Clients) == 0 {
val.Push(fmt.Errorf(errFmtOIDCNoClientsConfigured))
val.Push(fmt.Errorf(errFmtOIDCProviderNoClientsConfigured))
} else {
validateOIDCClients(config, val)
}
@ -55,157 +58,158 @@ func validateOIDC(config *schema.OpenIDConnectConfiguration, val *schema.StructV
func validateOIDCIssuer(config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
switch {
case config.IssuerPrivateKey != nil:
validateOIDCIssuerLegacy(config, val)
validateOIDCIssuerPrivateKey(config)
fallthrough
case len(config.IssuerJWKS) != 0:
validateOIDCIssuerModern(config, val)
case len(config.IssuerPrivateKeys) != 0:
validateOIDCIssuerPrivateKeys(config, val)
default:
val.Push(fmt.Errorf(errFmtOIDCNoPrivateKey))
val.Push(fmt.Errorf(errFmtOIDCProviderNoPrivateKey))
}
}
func validateOIDCIssuerLegacy(config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
j := &jose.JSONWebKey{Key: &config.IssuerPrivateKey.PublicKey}
thumbprint, err := j.Thumbprint(crypto.SHA1)
if err != nil {
val.Push(fmt.Errorf("identity_providers: oidc: option 'issuer_private_key' failed to calculate thumbprint to configure key id value: %w", err))
return
}
config.IssuerJWKS = append(config.IssuerJWKS, schema.JWK{
KeyID: fmt.Sprintf("%x", thumbprint)[:6],
func validateOIDCIssuerPrivateKey(config *schema.OpenIDConnectConfiguration) {
config.IssuerPrivateKeys = append([]schema.JWK{{
Algorithm: oidc.SigningAlgRSAUsingSHA256,
Use: oidc.KeyUseSignature,
Key: config.IssuerPrivateKey,
CertificateChain: config.IssuerCertificateChain,
})
}}, config.IssuerPrivateKeys...)
}
//nolint:gocyclo // Refactor time permitting.
func validateOIDCIssuerModern(config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
func jwkCalculateThumbprint(key schema.CryptographicKey) (thumbprintStr string, err error) {
j := jose.JSONWebKey{}
switch k := key.(type) {
case schema.CryptographicPrivateKey:
j.Key = k.Public()
case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey:
j.Key = k
default:
return "", nil
}
var thumbprint []byte
if thumbprint, err = j.Thumbprint(crypto.SHA256); err != nil {
return "", err
}
return fmt.Sprintf("%x", thumbprint)[:6], nil
}
func validateOIDCIssuerPrivateKeys(config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
var (
props *JWKProperties
err error
)
kids := make([]string, len(config.IssuerJWKS))
kids := make([]string, len(config.IssuerPrivateKeys))
for i := 0; i < len(config.IssuerJWKS); i++ {
if key, ok := config.IssuerJWKS[i].Key.(*rsa.PrivateKey); ok && key.PublicKey.N == nil {
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d: option 'key' must be a valid RSA private key but the provided data is malformed as it's missing the public key bits", i+1))
for i := 0; i < len(config.IssuerPrivateKeys); i++ {
if key, ok := config.IssuerPrivateKeys[i].Key.(*rsa.PrivateKey); ok && key.PublicKey.N == nil {
val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysInvalid, i+1))
continue
}
switch n := len(config.IssuerJWKS[i].KeyID); {
switch n := len(config.IssuerPrivateKeys[i].KeyID); {
case n == 0:
j := jose.JSONWebKey{}
switch key := config.IssuerJWKS[i].Key.(type) {
case schema.CryptographicPrivateKey:
j.Key = key.Public()
case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey:
j.Key = key
default:
break
}
if j.Key == nil {
break
}
var thumbprint []byte
if thumbprint, err = j.Thumbprint(crypto.SHA1); err != nil {
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d: option 'key' failed to calculate thumbprint to configure key id value: %w", i+1, err))
if config.IssuerPrivateKeys[i].KeyID, err = jwkCalculateThumbprint(config.IssuerPrivateKeys[i].Key); err != nil {
val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysCalcThumbprint, i+1, err))
continue
}
config.IssuerJWKS[i].KeyID = fmt.Sprintf("%x", thumbprint)[:6]
case n > 7:
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option `key_id`` must be 7 characters or less", i+1, config.IssuerJWKS[i].KeyID))
val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysKeyIDLength, i+1, config.IssuerPrivateKeys[i].KeyID))
}
if config.IssuerJWKS[i].KeyID != "" && utils.IsStringInSlice(config.IssuerJWKS[i].KeyID, kids) {
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option 'key_id' must be unique", i+1, config.IssuerJWKS[i].KeyID))
if config.IssuerPrivateKeys[i].KeyID != "" && utils.IsStringInSlice(config.IssuerPrivateKeys[i].KeyID, kids) {
val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysAttributeNotUnique, i+1, config.IssuerPrivateKeys[i].KeyID, attrOIDCKeyID))
}
kids[i] = config.IssuerJWKS[i].KeyID
kids[i] = config.IssuerPrivateKeys[i].KeyID
if !utils.IsStringAlphaNumeric(config.IssuerJWKS[i].KeyID) {
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option 'key_id' must only have alphanumeric characters", i+1, config.IssuerJWKS[i].KeyID))
if !utils.IsStringAlphaNumeric(config.IssuerPrivateKeys[i].KeyID) {
val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysKeyIDNotAlphaNumeric, i+1, config.IssuerPrivateKeys[i].KeyID))
}
if props, err = schemaJWKGetProperties(config.IssuerJWKS[i]); err != nil {
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option 'key' failed to get key properties: %w", i+1, config.IssuerJWKS[i].KeyID, err))
if props, err = schemaJWKGetProperties(config.IssuerPrivateKeys[i]); err != nil {
val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysProperties, i+1, config.IssuerPrivateKeys[i].KeyID, err))
continue
}
switch config.IssuerJWKS[i].Use {
case "":
config.IssuerJWKS[i].Use = props.Use
case oidc.KeyUseSignature:
break
default:
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option '%s' must be one of %s but it's configured as '%s'", i+1, config.IssuerJWKS[i].KeyID, "use", strJoinOr([]string{oidc.KeyUseSignature}), config.IssuerJWKS[i].Use))
}
validateOIDCIssuerPrivateKeysUseAlg(i, props, config, val)
validateOIDCIssuerPrivateKeyPair(i, config, val)
}
switch {
case config.IssuerJWKS[i].Algorithm == "":
config.IssuerJWKS[i].Algorithm = props.Algorithm
case utils.IsStringInSlice(config.IssuerJWKS[i].Algorithm, validOIDCIssuerJWKSigningAlgs):
break
default:
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option '%s' must be one of %s but it's configured as '%s'", i+1, config.IssuerJWKS[i].KeyID, "algorithm", strJoinOr(validOIDCIssuerJWKSigningAlgs), config.IssuerJWKS[i].Algorithm))
}
if len(config.Discovery.ResponseObjectSigningAlgs) != 0 && !utils.IsStringInSlice(oidc.SigningAlgRSAUsingSHA256, config.Discovery.ResponseObjectSigningAlgs) {
val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysNoRS256, oidc.SigningAlgRSAUsingSHA256, strJoinAnd(config.Discovery.ResponseObjectSigningAlgs)))
}
}
if config.IssuerJWKS[i].Algorithm != "" {
if utils.IsStringInSlice(config.IssuerJWKS[i].Algorithm, config.Discovery.RegisteredJWKSigningAlgs) {
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option 'algorithm' must be unique but another key is using it", i+1, config.IssuerJWKS[i].KeyID))
} else {
config.Discovery.RegisteredJWKSigningAlgs = append(config.Discovery.RegisteredJWKSigningAlgs, config.IssuerJWKS[i].Algorithm)
}
}
func validateOIDCIssuerPrivateKeysUseAlg(i int, props *JWKProperties, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
switch config.IssuerPrivateKeys[i].Use {
case "":
config.IssuerPrivateKeys[i].Use = props.Use
case oidc.KeyUseSignature:
break
default:
val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysInvalidOptionOneOf, i+1, config.IssuerPrivateKeys[i].KeyID, attrOIDCKeyUse, strJoinOr([]string{oidc.KeyUseSignature}), config.IssuerPrivateKeys[i].Use))
}
if config.IssuerJWKS[i].Algorithm == oidc.SigningAlgRSAUsingSHA256 && config.Discovery.DefaultKeyID == "" {
config.Discovery.DefaultKeyID = config.IssuerJWKS[i].KeyID
}
switch {
case config.IssuerPrivateKeys[i].Algorithm == "":
config.IssuerPrivateKeys[i].Algorithm = props.Algorithm
case utils.IsStringInSlice(config.IssuerPrivateKeys[i].Algorithm, validOIDCIssuerJWKSigningAlgs):
break
default:
val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysInvalidOptionOneOf, i+1, config.IssuerPrivateKeys[i].KeyID, attrOIDCAlgorithm, strJoinOr(validOIDCIssuerJWKSigningAlgs), config.IssuerPrivateKeys[i].Algorithm))
}
var checkEqualKey bool
switch key := config.IssuerJWKS[i].Key.(type) {
case *rsa.PrivateKey:
checkEqualKey = true
if key.Size() < 256 {
checkEqualKey = false
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option 'key' is an RSA %d bit private key but it must be a RSA 2048 bit private key", i+1, config.IssuerJWKS[i].KeyID, key.Size()*8))
}
case *ecdsa.PrivateKey:
checkEqualKey = true
default:
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option 'key' must be a *rsa.PrivateKey or *ecdsa.PrivateKey but it's a %T", i+1, config.IssuerJWKS[i].KeyID, key))
}
if config.IssuerJWKS[i].CertificateChain.HasCertificates() {
if checkEqualKey && !config.IssuerJWKS[i].CertificateChain.EqualKey(config.IssuerJWKS[i].Key) {
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option 'key' does not appear to be the private key the certificate provided by option 'certificate_chain'", i+1, config.IssuerJWKS[i].KeyID))
}
if err = config.IssuerJWKS[i].CertificateChain.Validate(); err != nil {
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option 'certificate_chain' produced an error during validation of the chain: %w", i+1, config.IssuerJWKS[i].KeyID, err))
}
if config.IssuerPrivateKeys[i].Algorithm != "" {
if utils.IsStringInSlice(config.IssuerPrivateKeys[i].Algorithm, config.Discovery.ResponseObjectSigningAlgs) {
val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysAttributeNotUnique, i+1, config.IssuerPrivateKeys[i].KeyID, attrOIDCAlgorithm))
} else {
config.Discovery.ResponseObjectSigningAlgs = append(config.Discovery.ResponseObjectSigningAlgs, config.IssuerPrivateKeys[i].Algorithm)
}
}
if len(config.Discovery.RegisteredJWKSigningAlgs) != 0 && !utils.IsStringInSlice(oidc.SigningAlgRSAUsingSHA256, config.Discovery.RegisteredJWKSigningAlgs) {
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: keys: must at least have one key supporting the '%s' algorithm but only has %s", oidc.SigningAlgRSAUsingSHA256, strJoinAnd(config.Discovery.RegisteredJWKSigningAlgs)))
if config.IssuerPrivateKeys[i].Algorithm == oidc.SigningAlgRSAUsingSHA256 && config.Discovery.DefaultKeyID == "" {
config.Discovery.DefaultKeyID = config.IssuerPrivateKeys[i].KeyID
}
}
func validateOIDCIssuerPrivateKeyPair(i int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
var (
checkEqualKey bool
err error
)
switch key := config.IssuerPrivateKeys[i].Key.(type) {
case *rsa.PrivateKey:
checkEqualKey = true
if key.Size() < 256 {
checkEqualKey = false
val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysRSAKeyLessThan2048Bits, i+1, config.IssuerPrivateKeys[i].KeyID, key.Size()*8))
}
case *ecdsa.PrivateKey:
checkEqualKey = true
default:
val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysKeyNotRSAOrECDSA, i+1, config.IssuerPrivateKeys[i].KeyID, key))
}
if config.IssuerPrivateKeys[i].CertificateChain.HasCertificates() {
if checkEqualKey && !config.IssuerPrivateKeys[i].CertificateChain.EqualKey(config.IssuerPrivateKeys[i].Key) {
val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysKeyCertificateMismatch, i+1, config.IssuerPrivateKeys[i].KeyID))
}
if err = config.IssuerPrivateKeys[i].CertificateChain.Validate(); err != nil {
val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysCertificateChainInvalid, i+1, config.IssuerPrivateKeys[i].KeyID, err))
}
}
}
@ -356,7 +360,7 @@ func validateOIDCClient(c int, config *schema.OpenIDConnectConfiguration, val *s
case policyOneFactor, policyTwoFactor:
break
default:
val.Push(fmt.Errorf(errFmtOIDCClientInvalidValue, config.Clients[c].ID, "policy", strJoinOr([]string{policyOneFactor, policyTwoFactor}), config.Clients[c].Policy))
val.Push(fmt.Errorf(errFmtOIDCClientInvalidValue, config.Clients[c].ID, "authorization_policy", strJoinOr([]string{policyOneFactor, policyTwoFactor}), config.Clients[c].Policy))
}
switch config.Clients[c].PKCEChallengeMethod {
@ -374,10 +378,113 @@ func validateOIDCClient(c int, config *schema.OpenIDConnectConfiguration, val *s
validateOIDCClientGrantTypes(c, config, val, errDeprecatedFunc)
validateOIDCClientRedirectURIs(c, config, val, errDeprecatedFunc)
validateOIDCClientTokenEndpointAuth(c, config, val)
validateOIDDClientSigningAlgs(c, config, val)
validateOIDCClientSectorIdentifier(c, config, val)
validateOIDCClientPublicKeys(c, config, val)
validateOIDCClientTokenEndpointAuth(c, config, val)
}
func validateOIDCClientPublicKeys(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
switch {
case config.Clients[c].PublicKeys.URI != nil && len(config.Clients[c].PublicKeys.Values) != 0:
val.Push(fmt.Errorf(errFmtOIDCClientPublicKeysBothURIAndValuesConfigured, config.Clients[c].ID))
case config.Clients[c].PublicKeys.URI != nil:
if config.Clients[c].PublicKeys.URI.Scheme != schemeHTTPS {
val.Push(fmt.Errorf(errFmtOIDCClientPublicKeysURIInvalidScheme, config.Clients[c].ID, config.Clients[c].PublicKeys.URI.Scheme))
}
case len(config.Clients[c].PublicKeys.Values) != 0:
validateOIDCClientJSONWebKeysList(c, config, val)
}
}
func validateOIDCClientJSONWebKeysList(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
var (
props *JWKProperties
err error
)
for i := 0; i < len(config.Clients[c].PublicKeys.Values); i++ {
if config.Clients[c].PublicKeys.Values[i].KeyID == "" {
val.Push(fmt.Errorf(errFmtOIDCClientPublicKeysInvalidOptionMissingOneOf, config.Clients[c].ID, i+1, attrOIDCKeyID))
}
if props, err = schemaJWKGetProperties(config.Clients[c].PublicKeys.Values[i]); err != nil {
val.Push(fmt.Errorf(errFmtOIDCClientPublicKeysProperties, config.Clients[c].ID, i+1, config.Clients[c].PublicKeys.Values[i].KeyID, err))
continue
}
validateOIDCClientJSONWebKeysListKeyUseAlg(c, i, props, config, val)
var checkEqualKey bool
switch key := config.Clients[c].PublicKeys.Values[i].Key.(type) {
case nil:
val.Push(fmt.Errorf(errFmtOIDCClientPublicKeysInvalidOptionMissingOneOf, config.Clients[c].ID, i+1, attrOIDCKey))
case *rsa.PublicKey:
checkEqualKey = true
if key.N == nil {
checkEqualKey = false
val.Push(fmt.Errorf(errFmtOIDCClientPublicKeysKeyMalformed, config.Clients[c].ID, i+1))
} else if key.Size() < 256 {
checkEqualKey = false
val.Push(fmt.Errorf(errFmtOIDCClientPublicKeysRSAKeyLessThan2048Bits, config.Clients[c].ID, i+1, config.Clients[c].PublicKeys.Values[i].KeyID, key.Size()*8))
}
case *ecdsa.PublicKey:
checkEqualKey = true
default:
val.Push(fmt.Errorf(errFmtOIDCClientPublicKeysKeyNotRSAOrECDSA, config.Clients[c].ID, i+1, config.Clients[c].PublicKeys.Values[i].KeyID, key))
}
if config.Clients[c].PublicKeys.Values[i].CertificateChain.HasCertificates() {
if checkEqualKey && !config.Clients[c].PublicKeys.Values[i].CertificateChain.EqualKey(config.Clients[c].PublicKeys.Values[i].Key) {
val.Push(fmt.Errorf(errFmtOIDCClientPublicKeysCertificateChainKeyMismatch, config.Clients[c].ID, i+1, config.Clients[c].PublicKeys.Values[i].KeyID))
}
if err = config.Clients[c].PublicKeys.Values[i].CertificateChain.Validate(); err != nil {
val.Push(fmt.Errorf(errFmtOIDCClientPublicKeysCertificateChainInvalid, config.Clients[c].ID, i+1, config.Clients[c].PublicKeys.Values[i].KeyID, err))
}
}
}
if config.Clients[c].RequestObjectSigningAlg != "" && !utils.IsStringInSlice(config.Clients[c].RequestObjectSigningAlg, config.Clients[c].Discovery.RequestObjectSigningAlgs) {
val.Push(fmt.Errorf(errFmtOIDCClientPublicKeysROSAMissingAlgorithm, config.Clients[c].ID, strJoinOr(config.Clients[c].Discovery.RequestObjectSigningAlgs)))
}
}
func validateOIDCClientJSONWebKeysListKeyUseAlg(c, i int, props *JWKProperties, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
switch config.Clients[c].PublicKeys.Values[i].Use {
case "":
config.Clients[c].PublicKeys.Values[i].Use = props.Use
case oidc.KeyUseSignature:
break
default:
val.Push(fmt.Errorf(errFmtOIDCClientPublicKeysInvalidOptionOneOf, config.Clients[c].ID, i+1, config.Clients[c].PublicKeys.Values[i].KeyID, attrOIDCKeyUse, strJoinOr([]string{oidc.KeyUseSignature}), config.Clients[c].PublicKeys.Values[i].Use))
}
switch {
case config.Clients[c].PublicKeys.Values[i].Algorithm == "":
config.Clients[c].PublicKeys.Values[i].Algorithm = props.Algorithm
case utils.IsStringInSlice(config.Clients[c].PublicKeys.Values[i].Algorithm, validOIDCIssuerJWKSigningAlgs):
break
default:
val.Push(fmt.Errorf(errFmtOIDCClientPublicKeysInvalidOptionOneOf, config.Clients[c].ID, i+1, config.Clients[c].PublicKeys.Values[i].KeyID, attrOIDCAlgorithm, strJoinOr(validOIDCIssuerJWKSigningAlgs), config.Clients[c].PublicKeys.Values[i].Algorithm))
}
if config.Clients[c].PublicKeys.Values[i].Algorithm != "" {
if !utils.IsStringInSlice(config.Clients[c].PublicKeys.Values[i].Algorithm, config.Discovery.RequestObjectSigningAlgs) {
config.Discovery.RequestObjectSigningAlgs = append(config.Discovery.RequestObjectSigningAlgs, config.Clients[c].PublicKeys.Values[i].Algorithm)
}
if !utils.IsStringInSlice(config.Clients[c].PublicKeys.Values[i].Algorithm, config.Clients[c].Discovery.RequestObjectSigningAlgs) {
config.Clients[c].Discovery.RequestObjectSigningAlgs = append(config.Clients[c].Discovery.RequestObjectSigningAlgs, config.Clients[c].PublicKeys.Values[i].Algorithm)
}
}
}
func validateOIDCClientSectorIdentifier(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
@ -654,11 +761,34 @@ func validateOIDCClientTokenEndpointAuth(c int, config *schema.OpenIDConnectConf
case "":
break
case oidc.ClientAuthMethodClientSecretJWT:
switch {
case config.Clients[c].TokenEndpointAuthSigningAlg == "":
config.Clients[c].TokenEndpointAuthSigningAlg = oidc.SigningAlgHMACUsingSHA256
case !utils.IsStringInSlice(config.Clients[c].TokenEndpointAuthSigningAlg, validOIDCClientTokenEndpointAuthSigAlgs):
val.Push(fmt.Errorf(errFmtOIDCClientInvalidTokenEndpointAuthSigAlg, config.Clients[c].ID, strJoinOr(validOIDCClientTokenEndpointAuthSigAlgs), config.Clients[c].TokenEndpointAuthMethod))
validateOIDCClientTokenEndpointAuthClientSecretJWT(c, config, val)
case oidc.ClientAuthMethodPrivateKeyJWT:
validateOIDCClientTokenEndpointAuthPublicKeyJWT(config.Clients[c], val)
}
}
func validateOIDCClientTokenEndpointAuthClientSecretJWT(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
switch {
case config.Clients[c].TokenEndpointAuthSigningAlg == "":
config.Clients[c].TokenEndpointAuthSigningAlg = oidc.SigningAlgHMACUsingSHA256
case !utils.IsStringInSlice(config.Clients[c].TokenEndpointAuthSigningAlg, validOIDCClientTokenEndpointAuthSigAlgsClientSecretJWT):
val.Push(fmt.Errorf(errFmtOIDCClientInvalidTokenEndpointAuthSigAlg, config.Clients[c].ID, strJoinOr(validOIDCClientTokenEndpointAuthSigAlgsClientSecretJWT), config.Clients[c].TokenEndpointAuthMethod))
}
}
func validateOIDCClientTokenEndpointAuthPublicKeyJWT(config schema.OpenIDConnectClientConfiguration, val *schema.StructValidator) {
switch {
case config.TokenEndpointAuthSigningAlg == "":
val.Push(fmt.Errorf(errFmtOIDCClientInvalidTokenEndpointAuthSigAlgMissingPrivateKeyJWT, config.ID))
case !utils.IsStringInSlice(config.TokenEndpointAuthSigningAlg, validOIDCIssuerJWKSigningAlgs):
val.Push(fmt.Errorf(errFmtOIDCClientInvalidTokenEndpointAuthSigAlg, config.ID, strJoinOr(validOIDCIssuerJWKSigningAlgs), config.TokenEndpointAuthMethod))
}
if config.PublicKeys.URI == nil {
if len(config.PublicKeys.Values) == 0 {
val.Push(fmt.Errorf(errFmtOIDCClientInvalidPublicKeysPrivateKeyJWT, config.ID))
} else if len(config.Discovery.RequestObjectSigningAlgs) != 0 && !utils.IsStringInSlice(config.TokenEndpointAuthSigningAlg, config.Discovery.RequestObjectSigningAlgs) {
val.Push(fmt.Errorf(errFmtOIDCClientInvalidTokenEndpointAuthSigAlgReg, config.ID, strJoinOr(config.Discovery.RequestObjectSigningAlgs), config.TokenEndpointAuthMethod))
}
}
}
@ -666,15 +796,15 @@ func validateOIDCClientTokenEndpointAuth(c int, config *schema.OpenIDConnectConf
func validateOIDDClientSigningAlgs(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
if config.Clients[c].UserinfoSigningAlg == "" {
config.Clients[c].UserinfoSigningAlg = schema.DefaultOpenIDConnectClientConfiguration.UserinfoSigningAlg
} else if config.Clients[c].UserinfoSigningAlg != oidc.SigningAlgNone && !utils.IsStringInSlice(config.Clients[c].UserinfoSigningAlg, config.Discovery.RegisteredJWKSigningAlgs) {
} else if config.Clients[c].UserinfoSigningAlg != oidc.SigningAlgNone && !utils.IsStringInSlice(config.Clients[c].UserinfoSigningAlg, config.Discovery.ResponseObjectSigningAlgs) {
val.Push(fmt.Errorf(errFmtOIDCClientInvalidValue,
config.Clients[c].ID, attrOIDCUsrSigAlg, strJoinOr(append(config.Discovery.RegisteredJWKSigningAlgs, oidc.SigningAlgNone)), config.Clients[c].UserinfoSigningAlg))
config.Clients[c].ID, attrOIDCUsrSigAlg, strJoinOr(append(config.Discovery.ResponseObjectSigningAlgs, oidc.SigningAlgNone)), config.Clients[c].UserinfoSigningAlg))
}
if config.Clients[c].IDTokenSigningAlg == "" {
config.Clients[c].IDTokenSigningAlg = schema.DefaultOpenIDConnectClientConfiguration.IDTokenSigningAlg
} else if !utils.IsStringInSlice(config.Clients[c].IDTokenSigningAlg, config.Discovery.RegisteredJWKSigningAlgs) {
} else if !utils.IsStringInSlice(config.Clients[c].IDTokenSigningAlg, config.Discovery.ResponseObjectSigningAlgs) {
val.Push(fmt.Errorf(errFmtOIDCClientInvalidValue,
config.Clients[c].ID, attrOIDCIDTokenSigAlg, strJoinOr(config.Discovery.RegisteredJWKSigningAlgs), config.Clients[c].IDTokenSigningAlg))
config.Clients[c].ID, attrOIDCIDTokenSigAlg, strJoinOr(config.Discovery.ResponseObjectSigningAlgs), config.Clients[c].IDTokenSigningAlg))
}
}

File diff suppressed because it is too large Load Diff

View File

@ -37,13 +37,7 @@ func validateNotifierTemplates(config *schema.NotifierConfiguration, validator *
return
}
var (
err error
)
_, err = os.Stat(config.TemplatePath)
switch {
switch _, err := os.Stat(config.TemplatePath); {
case os.IsNotExist(err):
validator.Push(fmt.Errorf(errFmtNotifierTemplatePathNotExist, config.TemplatePath))
return

View File

@ -8,6 +8,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/authelia/authelia/v4/internal/configuration/schema"
@ -284,3 +285,17 @@ func (suite *NotifierSuite) TestFileShouldEnsureFilenameIsProvided() {
func TestNotifierSuite(t *testing.T) {
suite.Run(t, new(NotifierSuite))
}
func TestNotifierMiscMissingTemplateTests(t *testing.T) {
config := &schema.NotifierConfiguration{
TemplatePath: string([]byte{0x0, 0x1}),
}
val := schema.NewStructValidator()
validateNotifierTemplates(config, val)
require.Len(t, val.Errors(), 1)
assert.EqualError(t, val.Errors()[0], "notifier: option 'template_path' refers to location '\x00\x01' which couldn't be opened: stat \x00\x01: invalid argument")
}

View File

@ -2,6 +2,7 @@ package validator
import (
"fmt"
"os"
"path"
"sort"
"strings"
@ -19,11 +20,11 @@ func ValidateServerTLS(config *schema.Configuration, validator *schema.StructVal
}
if config.Server.TLS.Key != "" {
validateFileExists(config.Server.TLS.Key, validator, "key")
validateServerTLSFileExists("key", config.Server.TLS.Key, validator)
}
if config.Server.TLS.Certificate != "" {
validateFileExists(config.Server.TLS.Certificate, validator, "certificate")
validateServerTLSFileExists("certificate", config.Server.TLS.Certificate, validator)
}
if config.Server.TLS.Key == "" && config.Server.TLS.Certificate == "" &&
@ -32,7 +33,24 @@ func ValidateServerTLS(config *schema.Configuration, validator *schema.StructVal
}
for _, clientCertPath := range config.Server.TLS.ClientCertificates {
validateFileExists(clientCertPath, validator, "client_certificates")
validateServerTLSFileExists("client_certificates", clientCertPath, validator)
}
}
// validateServerTLSFileExists checks whether a file exist.
func validateServerTLSFileExists(name, path string, validator *schema.StructValidator) {
var (
info os.FileInfo
err error
)
switch info, err = os.Stat(path); {
case os.IsNotExist(err):
validator.Push(fmt.Errorf("server: tls: option '%s' with path '%s' refers to a file that doesn't exist", name, path))
case err != nil:
validator.Push(fmt.Errorf("server: tls: option '%s' with path '%s' could not be verified due to a file system error: %w", name, path, err))
case info.IsDir():
validator.Push(fmt.Errorf("server: tls: option '%s' with path '%s' refers to a directory but it should refer to a file", name, path))
}
}
@ -197,13 +215,3 @@ func validateServerEndpointsAuthzStrategies(name string, strategies []schema.Ser
}
}
}
// validateFileExists checks whether a file exist.
func validateFileExists(path string, validator *schema.StructValidator, opt string) {
exist, err := utils.FileExists(path)
if err != nil {
validator.Push(fmt.Errorf(errFmtServerTLSFileNotExistErr, opt, path, err))
} else if !exist {
validator.Push(fmt.Errorf(errFmtServerTLSFileNotExist, opt, path))
}
}

View File

@ -1,6 +1,7 @@
package validator
import (
"fmt"
"os"
"testing"
"time"
@ -296,7 +297,7 @@ func TestShouldRaiseErrorWhenTLSCertDoesNotExist(t *testing.T) {
ValidateServer(&config, validator)
require.Len(t, validator.Errors(), 1)
assert.EqualError(t, validator.Errors()[0], "server: tls: option 'certificate' the file '/tmp/unexisting_file' does not exist")
assert.EqualError(t, validator.Errors()[0], "server: tls: option 'certificate' with path '/tmp/unexisting_file' refers to a file that doesn't exist")
}
func TestShouldRaiseErrorWhenTLSKeyWithoutCertIsProvided(t *testing.T) {
@ -329,7 +330,7 @@ func TestShouldRaiseErrorWhenTLSKeyDoesNotExist(t *testing.T) {
ValidateServer(&config, validator)
require.Len(t, validator.Errors(), 1)
assert.EqualError(t, validator.Errors()[0], "server: tls: option 'key' the file '/tmp/unexisting_file' does not exist")
assert.EqualError(t, validator.Errors()[0], "server: tls: option 'key' with path '/tmp/unexisting_file' refers to a file that doesn't exist")
}
func TestShouldNotRaiseErrorWhenBothTLSCertificateAndKeyAreProvided(t *testing.T) {
@ -373,7 +374,7 @@ func TestShouldRaiseErrorWhenTLSClientCertificateDoesNotExist(t *testing.T) {
ValidateServer(&config, validator)
require.Len(t, validator.Errors(), 1)
assert.EqualError(t, validator.Errors()[0], "server: tls: option 'client_certificates' the file '/tmp/unexisting' does not exist")
assert.EqualError(t, validator.Errors()[0], "server: tls: option 'client_certificates' with path '/tmp/unexisting' refers to a file that doesn't exist")
}
func TestShouldRaiseErrorWhenTLSClientAuthIsDefinedButNotServerCertificate(t *testing.T) {
@ -569,3 +570,25 @@ func TestServerAuthzEndpointLegacyAsImplementationLegacyWhenBlank(t *testing.T)
assert.Equal(t, authzImplementationLegacy, config.Server.Endpoints.Authz[legacy].Implementation)
}
func TestValidateTLSPathStatInvalidArgument(t *testing.T) {
val := schema.NewStructValidator()
validateServerTLSFileExists("key", string([]byte{0x0, 0x1}), val)
require.Len(t, val.Errors(), 1)
assert.EqualError(t, val.Errors()[0], "server: tls: option 'key' with path '\x00\x01' could not be verified due to a file system error: stat \x00\x01: invalid argument")
}
func TestValidateTLSPathIsDir(t *testing.T) {
dir := t.TempDir()
val := schema.NewStructValidator()
validateServerTLSFileExists("key", dir, val)
require.Len(t, val.Errors(), 1)
assert.EqualError(t, val.Errors()[0], fmt.Sprintf("server: tls: option 'key' with path '%s' refers to a directory but it should refer to a file", dir))
}

View File

@ -10,6 +10,10 @@ import (
// ValidateTLSConfig sets the default values and validates a schema.TLSConfig.
func ValidateTLSConfig(config *schema.TLSConfig, configDefault *schema.TLSConfig) (err error) {
if configDefault == nil {
return errors.New("must provide configDefault")
}
if config == nil {
return
}
@ -35,7 +39,7 @@ func ValidateTLSConfig(config *schema.TLSConfig, configDefault *schema.TLSConfig
}
if (config.CertificateChain.HasCertificates() || config.PrivateKey != nil) && !config.CertificateChain.EqualKey(config.PrivateKey) {
return errors.New("option 'certificates' is invalid: provided certificate is not the public key for the private key provided")
return errors.New("option 'certificates' is invalid: provided certificate does not contain the public key for the private key provided")
}
return nil

View File

@ -0,0 +1,30 @@
package validator
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/authelia/authelia/v4/internal/configuration/schema"
)
func TestValidateTLSConfig(t *testing.T) {
var (
config, configDefault *schema.TLSConfig
)
assert.EqualError(t, ValidateTLSConfig(config, configDefault), "must provide configDefault")
configDefault = &schema.TLSConfig{}
assert.NoError(t, ValidateTLSConfig(config, configDefault))
config = &schema.TLSConfig{}
assert.NoError(t, ValidateTLSConfig(config, configDefault))
config.PrivateKey = keyRSA2048
config.CertificateChain = certRSA4096
assert.EqualError(t, ValidateTLSConfig(config, configDefault), "option 'certificates' is invalid: provided certificate does not contain the public key for the private key provided")
}

View File

@ -125,12 +125,20 @@ type JWKProperties struct {
func schemaJWKGetProperties(jwk schema.JWK) (properties *JWKProperties, err error) {
switch key := jwk.Key.(type) {
case nil:
return nil, fmt.Errorf("private key is nil")
return nil, nil
case ed25519.PrivateKey, ed25519.PublicKey:
return &JWKProperties{}, nil
case *rsa.PrivateKey:
if key.PublicKey.N == nil {
return &JWKProperties{oidc.KeyUseSignature, oidc.SigningAlgRSAUsingSHA256, 0, nil}, nil
}
return &JWKProperties{oidc.KeyUseSignature, oidc.SigningAlgRSAUsingSHA256, key.Size(), nil}, nil
case *rsa.PublicKey:
if key.N == nil {
return &JWKProperties{oidc.KeyUseSignature, oidc.SigningAlgRSAUsingSHA256, 0, nil}, nil
}
return &JWKProperties{oidc.KeyUseSignature, oidc.SigningAlgRSAUsingSHA256, key.Size(), nil}, nil
case *ecdsa.PublicKey:
switch key.Curve {

View File

@ -1,9 +1,14 @@
package validator
import (
"crypto/elliptic"
"crypto/rsa"
"testing"
"github.com/stretchr/testify/assert"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/oidc"
)
func TestIsCookieDomainValid(t *testing.T) {
@ -38,3 +43,39 @@ func TestIsCookieDomainValid(t *testing.T) {
})
}
}
func TestBuildStringFuncsMissingTests(t *testing.T) {
assert.Equal(t, "", buildJoinedString(".", ":", "'", nil))
assert.Equal(t, "'abc', '123'", strJoinComma("", []string{"abc", "123"}))
}
func TestSchemaJWKGetPropertiesMissingTests(t *testing.T) {
props, err := schemaJWKGetProperties(schema.JWK{Key: keyECDSAP224})
assert.NoError(t, err)
assert.Equal(t, oidc.KeyUseSignature, props.Use)
assert.Equal(t, "", props.Algorithm)
assert.Equal(t, elliptic.P224(), props.Curve)
assert.Equal(t, -1, props.Bits)
props, err = schemaJWKGetProperties(schema.JWK{Key: keyECDSAP224.Public()})
assert.NoError(t, err)
assert.Equal(t, oidc.KeyUseSignature, props.Use)
assert.Equal(t, "", props.Algorithm)
assert.Equal(t, elliptic.P224(), props.Curve)
assert.Equal(t, -1, props.Bits)
rsa := &rsa.PrivateKey{}
*rsa = *keyRSA2048
rsa.PublicKey.N = nil
props, err = schemaJWKGetProperties(schema.JWK{Key: rsa})
assert.NoError(t, err)
assert.Equal(t, oidc.KeyUseSignature, props.Use)
assert.Equal(t, oidc.SigningAlgRSAUsingSHA256, props.Algorithm)
assert.Equal(t, nil, props.Curve)
assert.Equal(t, 0, props.Bits)
}

View File

@ -127,7 +127,7 @@ func OpenIDConnectAuthorization(ctx *middlewares.AutheliaCtx, rw http.ResponseWr
ctx.Logger.Debugf("Authorization Request with id '%s' on client with id '%s' was successfully processed, proceeding to build Authorization Response", requester.GetID(), clientID)
session := oidc.NewSessionWithAuthorizeRequest(issuer, ctx.Providers.OpenIDConnect.KeyManager.GetKIDFromAlg(ctx, client.GetIDTokenSigningAlg()),
session := oidc.NewSessionWithAuthorizeRequest(issuer, ctx.Providers.OpenIDConnect.KeyManager.GetKeyIDFromAlg(ctx, client.GetIDTokenSigningAlg()),
userSession.Username, userSession.AuthenticationMethodRefs.MarshalRFC8176(), extraClaims, authTime, consent, requester)
ctx.Logger.Tracef("Authorization Request with id '%s' on client with id '%s' creating session for Authorization Response for subject '%s' with username '%s' with claims: %+v",

View File

@ -1,10 +1,12 @@
package oidc
package oidc_test
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/authelia/authelia/v4/internal/oidc"
)
type testAMRWant struct {
@ -17,13 +19,13 @@ type testAMRWant struct {
func TestAuthenticationMethodsReferences(t *testing.T) {
testCases := []struct {
desc string
is AuthenticationMethodsReferences
is oidc.AuthenticationMethodsReferences
want testAMRWant
}{
{
desc: "Username and Password",
is: AuthenticationMethodsReferences{UsernameAndPassword: true},
is: oidc.AuthenticationMethodsReferences{UsernameAndPassword: true},
want: testAMRWant{
FactorKnowledge: true,
FactorPossession: false,
@ -37,7 +39,7 @@ func TestAuthenticationMethodsReferences(t *testing.T) {
{
desc: "TOTP",
is: AuthenticationMethodsReferences{TOTP: true},
is: oidc.AuthenticationMethodsReferences{TOTP: true},
want: testAMRWant{
FactorKnowledge: false,
FactorPossession: true,
@ -51,7 +53,7 @@ func TestAuthenticationMethodsReferences(t *testing.T) {
{
desc: "WebAuthn",
is: AuthenticationMethodsReferences{WebAuthn: true},
is: oidc.AuthenticationMethodsReferences{WebAuthn: true},
want: testAMRWant{
FactorKnowledge: false,
FactorPossession: true,
@ -65,7 +67,7 @@ func TestAuthenticationMethodsReferences(t *testing.T) {
{
desc: "WebAuthn User Presence",
is: AuthenticationMethodsReferences{WebAuthnUserPresence: true},
is: oidc.AuthenticationMethodsReferences{WebAuthnUserPresence: true},
want: testAMRWant{
FactorKnowledge: false,
FactorPossession: false,
@ -79,7 +81,7 @@ func TestAuthenticationMethodsReferences(t *testing.T) {
{
desc: "WebAuthn User Verified",
is: AuthenticationMethodsReferences{WebAuthnUserVerified: true},
is: oidc.AuthenticationMethodsReferences{WebAuthnUserVerified: true},
want: testAMRWant{
FactorKnowledge: false,
FactorPossession: false,
@ -93,7 +95,7 @@ func TestAuthenticationMethodsReferences(t *testing.T) {
{
desc: "WebAuthn with User Presence and Verified",
is: AuthenticationMethodsReferences{WebAuthn: true, WebAuthnUserVerified: true, WebAuthnUserPresence: true},
is: oidc.AuthenticationMethodsReferences{WebAuthn: true, WebAuthnUserVerified: true, WebAuthnUserPresence: true},
want: testAMRWant{
FactorKnowledge: false,
FactorPossession: true,
@ -107,7 +109,7 @@ func TestAuthenticationMethodsReferences(t *testing.T) {
{
desc: "Duo",
is: AuthenticationMethodsReferences{Duo: true},
is: oidc.AuthenticationMethodsReferences{Duo: true},
want: testAMRWant{
FactorKnowledge: false,
FactorPossession: true,
@ -121,7 +123,7 @@ func TestAuthenticationMethodsReferences(t *testing.T) {
{
desc: "Duo WebAuthn TOTP",
is: AuthenticationMethodsReferences{Duo: true, WebAuthn: true, TOTP: true},
is: oidc.AuthenticationMethodsReferences{Duo: true, WebAuthn: true, TOTP: true},
want: testAMRWant{
FactorKnowledge: false,
FactorPossession: true,
@ -135,7 +137,7 @@ func TestAuthenticationMethodsReferences(t *testing.T) {
{
desc: "Duo TOTP",
is: AuthenticationMethodsReferences{Duo: true, TOTP: true},
is: oidc.AuthenticationMethodsReferences{Duo: true, TOTP: true},
want: testAMRWant{
FactorKnowledge: false,
FactorPossession: true,
@ -149,7 +151,7 @@ func TestAuthenticationMethodsReferences(t *testing.T) {
{
desc: "Username and Password with Duo",
is: AuthenticationMethodsReferences{Duo: true, UsernameAndPassword: true},
is: oidc.AuthenticationMethodsReferences{Duo: true, UsernameAndPassword: true},
want: testAMRWant{
FactorKnowledge: true,
FactorPossession: true,

View File

@ -10,6 +10,8 @@ import (
"net/url"
"time"
"github.com/go-crypt/crypt"
"github.com/go-crypt/crypt/algorithm"
"github.com/go-crypt/crypt/algorithm/plaintext"
"github.com/golang-jwt/jwt/v4"
"github.com/ory/fosite"
@ -20,6 +22,46 @@ import (
"github.com/authelia/authelia/v4/internal/configuration/schema"
)
// NewHasher returns a new Hasher.
func NewHasher() (hasher *Hasher, err error) {
hasher = &Hasher{}
if hasher.decoder, err = crypt.NewDefaultDecoder(); err != nil {
return nil, err
}
if err = plaintext.RegisterDecoderPlainText(hasher.decoder); err != nil {
return nil, err
}
return hasher, nil
}
// Hasher implements the fosite.Hasher interface and adaptively compares hashes.
type Hasher struct {
decoder algorithm.DecoderRegister
}
// Compare compares the hash with the data and returns an error if they don't match.
func (h Hasher) Compare(_ context.Context, hash, data []byte) (err error) {
var digest algorithm.Digest
if digest, err = h.decoder.Decode(string(hash)); err != nil {
return err
}
if digest.MatchBytes(data) {
return nil
}
return errPasswordsDoNotMatch
}
// Hash creates a new hash from data.
func (h Hasher) Hash(_ context.Context, data []byte) (hash []byte, err error) {
return data, nil
}
// DefaultClientAuthenticationStrategy is a copy of fosite's with the addition of the client_secret_jwt method and some
// minor superficial changes.
//

View File

@ -1,14 +1,10 @@
package handlers
package oidc_test
import (
"context"
"crypto/ecdsa"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"database/sql"
"encoding/base64"
"encoding/pem"
"fmt"
"io"
"net/http"
@ -21,6 +17,8 @@ import (
"github.com/golang/mock/gomock"
"github.com/google/uuid"
"github.com/ory/fosite"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/valyala/fasthttp"
@ -31,6 +29,58 @@ import (
"github.com/authelia/authelia/v4/internal/oidc"
)
func TestShouldNotRaiseErrorOnEqualPasswordsPlainText(t *testing.T) {
hasher, err := oidc.NewHasher()
require.NoError(t, err)
a := []byte("$plaintext$abc")
b := []byte("abc")
ctx := context.Background()
assert.NoError(t, hasher.Compare(ctx, a, b))
}
func TestShouldNotRaiseErrorOnEqualPasswordsPlainTextWithSeparator(t *testing.T) {
hasher, err := oidc.NewHasher()
require.NoError(t, err)
a := []byte("$plaintext$abc$123")
b := []byte("abc$123")
ctx := context.Background()
assert.NoError(t, hasher.Compare(ctx, a, b))
}
func TestShouldRaiseErrorOnNonEqualPasswordsPlainText(t *testing.T) {
hasher, err := oidc.NewHasher()
require.NoError(t, err)
a := []byte("$plaintext$abc")
b := []byte("abcd")
ctx := context.Background()
assert.EqualError(t, hasher.Compare(ctx, a, b), "The provided client secret did not match the registered client secret.")
}
func TestShouldHashPassword(t *testing.T) {
hasher := oidc.Hasher{}
data := []byte("abc")
ctx := context.Background()
hash, err := hasher.Hash(ctx, data)
assert.NoError(t, err)
assert.Equal(t, data, hash)
}
func TestClientAuthenticationStrategySuite(t *testing.T) {
suite.Run(t, &ClientAuthenticationStrategySuite{})
}
@ -135,7 +185,9 @@ func (s *ClientAuthenticationStrategySuite) GetAssertionRequest(token string) (r
}
func (s *ClientAuthenticationStrategySuite) GetCtx() oidc.OpenIDConnectContext {
return &oidc.MockOpenIDConnectContext{
fmt.Println(s.GetIssuerURL())
return &MockOpenIDConnectContext{
Context: context.Background(),
MockIssuerURL: s.GetIssuerURL(),
}
@ -145,13 +197,13 @@ func (s *ClientAuthenticationStrategySuite) SetupTest() {
s.ctrl = gomock.NewController(s.T())
s.store = mocks.NewMockStorage(s.ctrl)
secret := MustDecodeSecret("$plaintext$client-secret")
secret := tOpenIDConnectPlainTextClientSecret
s.provider = oidc.NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
IssuerJWKS: []schema.JWK{},
IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: MustParseRSAPrivateKey(exampleRSAPrivateKey),
HMACSecret: "abc123",
IssuerPrivateKeys: []schema.JWK{
{Key: keyRSA2048, CertificateChain: certRSA2048, Use: oidc.KeyUseSignature, Algorithm: oidc.SigningAlgRSAUsingSHA256},
},
HMACSecret: "abc123",
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "hs256",
@ -184,7 +236,7 @@ func (s *ClientAuthenticationStrategySuite) SetupTest() {
TokenEndpointAuthSigningAlg: oidc.SigningAlgHMACUsingSHA512,
},
{
ID: "rs256",
ID: rs256,
Secret: secret,
Policy: authorization.OneFactor.String(),
RedirectURIs: []string{
@ -273,6 +325,142 @@ func (s *ClientAuthenticationStrategySuite) SetupTest() {
TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretJWT,
TokenEndpointAuthSigningAlg: oidc.SigningAlgECDSAUsingP521AndSHA512,
},
{
ID: "rs256k",
Secret: secret,
Policy: authorization.OneFactor.String(),
RedirectURIs: []string{
"https://client.example.com",
},
TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretJWT,
TokenEndpointAuthSigningAlg: oidc.SigningAlgRSAUsingSHA256,
PublicKeys: schema.OpenIDConnectClientPublicKeys{
Values: []schema.JWK{
{KeyID: rs256, Key: keyRSA2048.PublicKey, Algorithm: oidc.SigningAlgRSAUsingSHA256, Use: oidc.KeyUseSignature},
},
},
},
{
ID: "rs384k",
Secret: secret,
Policy: authorization.OneFactor.String(),
RedirectURIs: []string{
"https://client.example.com",
},
TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretJWT,
TokenEndpointAuthSigningAlg: oidc.SigningAlgRSAUsingSHA384,
PublicKeys: schema.OpenIDConnectClientPublicKeys{
Values: []schema.JWK{
{KeyID: "rs384", Key: keyRSA2048.PublicKey, Algorithm: oidc.SigningAlgRSAUsingSHA384, Use: oidc.KeyUseSignature},
},
},
},
{
ID: "rs512k",
Secret: secret,
Policy: authorization.OneFactor.String(),
RedirectURIs: []string{
"https://client.example.com",
},
TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretJWT,
TokenEndpointAuthSigningAlg: oidc.SigningAlgRSAUsingSHA512,
PublicKeys: schema.OpenIDConnectClientPublicKeys{
Values: []schema.JWK{
{KeyID: "rs512", Key: keyRSA2048.PublicKey, Algorithm: oidc.SigningAlgRSAUsingSHA512, Use: oidc.KeyUseSignature},
},
},
},
{
ID: "ps256k",
Secret: secret,
Policy: authorization.OneFactor.String(),
RedirectURIs: []string{
"https://client.example.com",
},
TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretJWT,
TokenEndpointAuthSigningAlg: oidc.SigningAlgRSAPSSUsingSHA256,
PublicKeys: schema.OpenIDConnectClientPublicKeys{
Values: []schema.JWK{
{KeyID: "ps256", Key: keyRSA2048.PublicKey, Algorithm: oidc.SigningAlgRSAPSSUsingSHA256, Use: oidc.KeyUseSignature},
},
},
},
{
ID: "ps384k",
Secret: secret,
Policy: authorization.OneFactor.String(),
RedirectURIs: []string{
"https://client.example.com",
},
TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretJWT,
TokenEndpointAuthSigningAlg: oidc.SigningAlgRSAPSSUsingSHA384,
PublicKeys: schema.OpenIDConnectClientPublicKeys{
Values: []schema.JWK{
{KeyID: "ps384", Key: keyRSA2048.PublicKey, Algorithm: oidc.SigningAlgRSAPSSUsingSHA384, Use: oidc.KeyUseSignature},
},
},
},
{
ID: "ps512k",
Secret: secret,
Policy: authorization.OneFactor.String(),
RedirectURIs: []string{
"https://client.example.com",
},
TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretJWT,
TokenEndpointAuthSigningAlg: oidc.SigningAlgRSAPSSUsingSHA512,
PublicKeys: schema.OpenIDConnectClientPublicKeys{
Values: []schema.JWK{
{KeyID: "ps512", Key: keyRSA2048.PublicKey, Algorithm: oidc.SigningAlgRSAPSSUsingSHA512, Use: oidc.KeyUseSignature},
},
},
},
{
ID: "es256k",
Secret: secret,
Policy: authorization.OneFactor.String(),
RedirectURIs: []string{
"https://client.example.com",
},
TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretJWT,
TokenEndpointAuthSigningAlg: oidc.SigningAlgECDSAUsingP256AndSHA256,
PublicKeys: schema.OpenIDConnectClientPublicKeys{
Values: []schema.JWK{
{KeyID: "es256", Key: keyECDSAP256.PublicKey, Algorithm: oidc.SigningAlgECDSAUsingP256AndSHA256, Use: oidc.KeyUseSignature},
},
},
},
{
ID: "es384k",
Secret: secret,
Policy: authorization.OneFactor.String(),
RedirectURIs: []string{
"https://client.example.com",
},
TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretJWT,
TokenEndpointAuthSigningAlg: oidc.SigningAlgECDSAUsingP384AndSHA384,
PublicKeys: schema.OpenIDConnectClientPublicKeys{
Values: []schema.JWK{
{KeyID: "es384", Key: keyECDSAP384.PublicKey, Algorithm: oidc.SigningAlgECDSAUsingP384AndSHA384, Use: oidc.KeyUseSignature},
},
},
},
{
ID: "es512k",
Secret: secret,
Policy: authorization.OneFactor.String(),
RedirectURIs: []string{
"https://client.example.com",
},
TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretJWT,
TokenEndpointAuthSigningAlg: oidc.SigningAlgECDSAUsingP521AndSHA512,
PublicKeys: schema.OpenIDConnectClientPublicKeys{
Values: []schema.JWK{
{KeyID: "es512", Key: keyECDSAP521.PublicKey, Algorithm: oidc.SigningAlgECDSAUsingP521AndSHA512, Use: oidc.KeyUseSignature},
},
},
},
{
ID: "hs5122",
Secret: secret,
@ -471,7 +659,7 @@ func (s *ClientAuthenticationStrategySuite) TestShouldValidateAssertionHS512() {
}
func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnMismatchedAlg() {
assertion := NewAssertion("rs256", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0))
assertion := NewAssertion(rs256, s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0))
assertionJWT := jwt.NewWithClaims(jwt.SigningMethodHS512, assertion)
@ -507,11 +695,11 @@ func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnMismatchedAlgS
}
func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnUnregisteredKeysRS256() {
assertion := NewAssertion("rs256", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0))
assertion := NewAssertion(rs256, s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0))
assertionJWT := jwt.NewWithClaims(jwt.SigningMethodRS256, assertion)
token, err := assertionJWT.SignedString(MustParseRSAPrivateKey(exampleRSAPrivateKey))
token, err := assertionJWT.SignedString(keyRSA2048)
s.Require().NoError(err)
s.Require().NotEqual("", token)
@ -524,12 +712,100 @@ func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnUnregisteredKe
s.Nil(client)
}
func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnBadAlgRS256() {
assertion := NewAssertion("rs256k", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0))
assertionJWT := jwt.NewWithClaims(jwt.SigningMethodPS256, assertion)
assertionJWT.Header[oidc.JWTHeaderKeyIdentifier] = rs256
token, err := assertionJWT.SignedString(keyRSA2048)
s.Require().NoError(err)
s.Require().NotEqual("", token)
r := s.GetAssertionRequest(token)
client, err := s.provider.DefaultClientAuthenticationStrategy(s.GetCtx(), r, r.PostForm)
s.EqualError(ErrorToRFC6749ErrorTest(err), "Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method). The 'client_assertion' uses signing algorithm 'PS256' but the requested OAuth 2.0 Client enforces signing algorithm 'RS256'.")
s.Nil(client)
}
func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnBadKidRS256() {
assertion := NewAssertion("rs256k", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0))
assertionJWT := jwt.NewWithClaims(jwt.SigningMethodRS256, assertion)
assertionJWT.Header[oidc.JWTHeaderKeyIdentifier] = "nokey"
token, err := assertionJWT.SignedString(keyRSA2048)
s.Require().NoError(err)
s.Require().NotEqual("", token)
r := s.GetAssertionRequest(token)
client, err := s.provider.DefaultClientAuthenticationStrategy(s.GetCtx(), r, r.PostForm)
s.EqualError(ErrorToRFC6749ErrorTest(err), "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. The JSON Web Token uses signing key with kid 'nokey', which could not be found.")
s.Nil(client)
}
func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnBadTypRS256() {
assertion := NewAssertion("rs256k", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0))
assertionJWT := jwt.NewWithClaims(jwt.SigningMethodES256, assertion)
assertionJWT.Header[oidc.JWTHeaderKeyIdentifier] = rs256
token, err := assertionJWT.SignedString(keyECDSAP256)
s.Require().NoError(err)
s.Require().NotEqual("", token)
r := s.GetAssertionRequest(token)
client, err := s.provider.DefaultClientAuthenticationStrategy(s.GetCtx(), r, r.PostForm)
s.EqualError(ErrorToRFC6749ErrorTest(err), "Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method). The 'client_assertion' uses signing algorithm 'ES256' but the requested OAuth 2.0 Client enforces signing algorithm 'RS256'.")
s.Nil(client)
}
func (s *ClientAuthenticationStrategySuite) TestShouldAuthKeysRS256() {
assertion := NewAssertion("rs256k", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0))
assertionJWT := jwt.NewWithClaims(jwt.SigningMethodRS256, assertion)
assertionJWT.Header[oidc.JWTHeaderKeyIdentifier] = rs256
token, err := assertionJWT.SignedString(keyRSA2048)
s.Require().NoError(err)
s.Require().NotEqual("", token)
r := s.GetAssertionRequest(token)
sig := fmt.Sprintf("%x", sha256.Sum256([]byte(assertion.ID)))
ctx := s.GetCtx()
gomock.InOrder(
s.store.
EXPECT().LoadOAuth2BlacklistedJTI(ctx, sig).
Return(nil, sql.ErrNoRows),
s.store.
EXPECT().SaveOAuth2BlacklistedJTI(ctx, model.OAuth2BlacklistedJTI{Signature: sig, ExpiresAt: assertion.ExpiresAt.Time}).
Return(nil),
)
client, err := s.provider.DefaultClientAuthenticationStrategy(s.GetCtx(), r, r.PostForm)
s.NoError(err)
s.Require().NotNil(client)
s.Equal("rs256k", client.GetID())
}
func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnUnregisteredKeysRS384() {
assertion := NewAssertion("rs384", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0))
assertionJWT := jwt.NewWithClaims(jwt.SigningMethodRS384, assertion)
token, err := assertionJWT.SignedString(MustParseRSAPrivateKey(exampleRSAPrivateKey))
token, err := assertionJWT.SignedString(keyRSA2048)
s.Require().NoError(err)
s.Require().NotEqual("", token)
@ -542,12 +818,46 @@ func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnUnregisteredKe
s.Nil(client)
}
func (s *ClientAuthenticationStrategySuite) TestShouldAuthKeysRS384() {
assertion := NewAssertion("rs384k", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0))
assertionJWT := jwt.NewWithClaims(jwt.SigningMethodRS384, assertion)
assertionJWT.Header[oidc.JWTHeaderKeyIdentifier] = "rs384"
token, err := assertionJWT.SignedString(keyRSA2048)
s.Require().NoError(err)
s.Require().NotEqual("", token)
r := s.GetAssertionRequest(token)
sig := fmt.Sprintf("%x", sha256.Sum256([]byte(assertion.ID)))
ctx := s.GetCtx()
gomock.InOrder(
s.store.
EXPECT().LoadOAuth2BlacklistedJTI(ctx, sig).
Return(nil, sql.ErrNoRows),
s.store.
EXPECT().SaveOAuth2BlacklistedJTI(ctx, model.OAuth2BlacklistedJTI{Signature: sig, ExpiresAt: assertion.ExpiresAt.Time}).
Return(nil),
)
client, err := s.provider.DefaultClientAuthenticationStrategy(s.GetCtx(), r, r.PostForm)
s.NoError(err)
s.Require().NotNil(client)
s.Equal("rs384k", client.GetID())
}
func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnUnregisteredKeysRS512() {
assertion := NewAssertion("rs512", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0))
assertionJWT := jwt.NewWithClaims(jwt.SigningMethodRS512, assertion)
token, err := assertionJWT.SignedString(MustParseRSAPrivateKey(exampleRSAPrivateKey))
token, err := assertionJWT.SignedString(keyRSA2048)
s.Require().NoError(err)
s.Require().NotEqual("", token)
@ -560,12 +870,46 @@ func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnUnregisteredKe
s.Nil(client)
}
func (s *ClientAuthenticationStrategySuite) TestShouldAuthKeysRS512() {
assertion := NewAssertion("rs512k", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0))
assertionJWT := jwt.NewWithClaims(jwt.SigningMethodRS512, assertion)
assertionJWT.Header[oidc.JWTHeaderKeyIdentifier] = "rs512"
token, err := assertionJWT.SignedString(keyRSA2048)
s.Require().NoError(err)
s.Require().NotEqual("", token)
r := s.GetAssertionRequest(token)
sig := fmt.Sprintf("%x", sha256.Sum256([]byte(assertion.ID)))
ctx := s.GetCtx()
gomock.InOrder(
s.store.
EXPECT().LoadOAuth2BlacklistedJTI(ctx, sig).
Return(nil, sql.ErrNoRows),
s.store.
EXPECT().SaveOAuth2BlacklistedJTI(ctx, model.OAuth2BlacklistedJTI{Signature: sig, ExpiresAt: assertion.ExpiresAt.Time}).
Return(nil),
)
client, err := s.provider.DefaultClientAuthenticationStrategy(s.GetCtx(), r, r.PostForm)
s.NoError(err)
s.Require().NotNil(client)
s.Equal("rs512k", client.GetID())
}
func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnUnregisteredKeysPS256() {
assertion := NewAssertion("ps256", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0))
assertionJWT := jwt.NewWithClaims(jwt.SigningMethodPS256, assertion)
token, err := assertionJWT.SignedString(MustParseRSAPrivateKey(exampleRSAPrivateKey))
token, err := assertionJWT.SignedString(keyRSA2048)
s.Require().NoError(err)
s.Require().NotEqual("", token)
@ -578,12 +922,46 @@ func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnUnregisteredKe
s.Nil(client)
}
func (s *ClientAuthenticationStrategySuite) TestShouldAuthKeysPS256() {
assertion := NewAssertion("ps256k", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0))
assertionJWT := jwt.NewWithClaims(jwt.SigningMethodPS256, assertion)
assertionJWT.Header[oidc.JWTHeaderKeyIdentifier] = "ps256"
token, err := assertionJWT.SignedString(keyRSA2048)
s.Require().NoError(err)
s.Require().NotEqual("", token)
r := s.GetAssertionRequest(token)
sig := fmt.Sprintf("%x", sha256.Sum256([]byte(assertion.ID)))
ctx := s.GetCtx()
gomock.InOrder(
s.store.
EXPECT().LoadOAuth2BlacklistedJTI(ctx, sig).
Return(nil, sql.ErrNoRows),
s.store.
EXPECT().SaveOAuth2BlacklistedJTI(ctx, model.OAuth2BlacklistedJTI{Signature: sig, ExpiresAt: assertion.ExpiresAt.Time}).
Return(nil),
)
client, err := s.provider.DefaultClientAuthenticationStrategy(s.GetCtx(), r, r.PostForm)
s.NoError(err)
s.Require().NotNil(client)
s.Equal("ps256k", client.GetID())
}
func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnUnregisteredKeysPS384() {
assertion := NewAssertion("ps384", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0))
assertionJWT := jwt.NewWithClaims(jwt.SigningMethodPS384, assertion)
token, err := assertionJWT.SignedString(MustParseRSAPrivateKey(exampleRSAPrivateKey))
token, err := assertionJWT.SignedString(keyRSA2048)
s.Require().NoError(err)
s.Require().NotEqual("", token)
@ -596,12 +974,46 @@ func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnUnregisteredKe
s.Nil(client)
}
func (s *ClientAuthenticationStrategySuite) TestShouldAuthKeysPS384() {
assertion := NewAssertion("ps384k", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0))
assertionJWT := jwt.NewWithClaims(jwt.SigningMethodPS384, assertion)
assertionJWT.Header[oidc.JWTHeaderKeyIdentifier] = "ps384"
token, err := assertionJWT.SignedString(keyRSA2048)
s.Require().NoError(err)
s.Require().NotEqual("", token)
r := s.GetAssertionRequest(token)
sig := fmt.Sprintf("%x", sha256.Sum256([]byte(assertion.ID)))
ctx := s.GetCtx()
gomock.InOrder(
s.store.
EXPECT().LoadOAuth2BlacklistedJTI(ctx, sig).
Return(nil, sql.ErrNoRows),
s.store.
EXPECT().SaveOAuth2BlacklistedJTI(ctx, model.OAuth2BlacklistedJTI{Signature: sig, ExpiresAt: assertion.ExpiresAt.Time}).
Return(nil),
)
client, err := s.provider.DefaultClientAuthenticationStrategy(s.GetCtx(), r, r.PostForm)
s.NoError(err)
s.Require().NotNil(client)
s.Equal("ps384k", client.GetID())
}
func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnUnregisteredKeysPS512() {
assertion := NewAssertion("ps512", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0))
assertionJWT := jwt.NewWithClaims(jwt.SigningMethodPS512, assertion)
token, err := assertionJWT.SignedString(MustParseRSAPrivateKey(exampleRSAPrivateKey))
token, err := assertionJWT.SignedString(keyRSA2048)
s.Require().NoError(err)
s.Require().NotEqual("", token)
@ -614,12 +1026,46 @@ func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnUnregisteredKe
s.Nil(client)
}
func (s *ClientAuthenticationStrategySuite) TestShouldAuthKeysPS512() {
assertion := NewAssertion("ps512k", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0))
assertionJWT := jwt.NewWithClaims(jwt.SigningMethodPS512, assertion)
assertionJWT.Header[oidc.JWTHeaderKeyIdentifier] = "ps512"
token, err := assertionJWT.SignedString(keyRSA2048)
s.Require().NoError(err)
s.Require().NotEqual("", token)
r := s.GetAssertionRequest(token)
sig := fmt.Sprintf("%x", sha256.Sum256([]byte(assertion.ID)))
ctx := s.GetCtx()
gomock.InOrder(
s.store.
EXPECT().LoadOAuth2BlacklistedJTI(ctx, sig).
Return(nil, sql.ErrNoRows),
s.store.
EXPECT().SaveOAuth2BlacklistedJTI(ctx, model.OAuth2BlacklistedJTI{Signature: sig, ExpiresAt: assertion.ExpiresAt.Time}).
Return(nil),
)
client, err := s.provider.DefaultClientAuthenticationStrategy(s.GetCtx(), r, r.PostForm)
s.NoError(err)
s.Require().NotNil(client)
s.Equal("ps512k", client.GetID())
}
func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnUnregisteredKeysES256() {
assertion := NewAssertion("es256", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0))
assertionJWT := jwt.NewWithClaims(jwt.SigningMethodES256, assertion)
token, err := assertionJWT.SignedString(MustParseECPrivateKey(exampleECP256PrivateKey))
token, err := assertionJWT.SignedString(keyECDSAP256)
s.Require().NoError(err)
s.Require().NotEqual("", token)
@ -632,12 +1078,47 @@ func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnUnregisteredKe
s.Nil(client)
}
func (s *ClientAuthenticationStrategySuite) TestShouldAuthKeysES256() {
assertion := NewAssertion("es256k", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0))
assertionJWT := jwt.NewWithClaims(jwt.SigningMethodES256, assertion)
assertionJWT.Header[oidc.JWTHeaderKeyIdentifier] = "es256"
token, err := assertionJWT.SignedString(keyECDSAP256)
s.Require().NoError(err)
s.Require().NotEqual("", token)
r := s.GetAssertionRequest(token)
sig := fmt.Sprintf("%x", sha256.Sum256([]byte(assertion.ID)))
ctx := s.GetCtx()
gomock.InOrder(
s.store.
EXPECT().LoadOAuth2BlacklistedJTI(ctx, sig).
Return(nil, sql.ErrNoRows),
s.store.
EXPECT().SaveOAuth2BlacklistedJTI(ctx, model.OAuth2BlacklistedJTI{Signature: sig, ExpiresAt: assertion.ExpiresAt.Time}).
Return(nil),
)
client, err := s.provider.DefaultClientAuthenticationStrategy(s.GetCtx(), r, r.PostForm)
s.NoError(err)
s.Require().NotNil(client)
s.Equal("es256k", client.GetID())
}
func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnUnregisteredKeysES384() {
assertion := NewAssertion("es384", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0))
assertionJWT := jwt.NewWithClaims(jwt.SigningMethodES384, assertion)
token, err := assertionJWT.SignedString(MustParseECPrivateKey(exampleECP384PrivateKey))
token, err := assertionJWT.SignedString(keyECDSAP384)
s.Require().NoError(err)
s.Require().NotEqual("", token)
@ -650,12 +1131,46 @@ func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnUnregisteredKe
s.Nil(client)
}
func (s *ClientAuthenticationStrategySuite) TestShouldAuthKeysES384() {
assertion := NewAssertion("es384k", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0))
assertionJWT := jwt.NewWithClaims(jwt.SigningMethodES384, assertion)
assertionJWT.Header[oidc.JWTHeaderKeyIdentifier] = "es384"
token, err := assertionJWT.SignedString(keyECDSAP384)
s.Require().NoError(err)
s.Require().NotEqual("", token)
r := s.GetAssertionRequest(token)
sig := fmt.Sprintf("%x", sha256.Sum256([]byte(assertion.ID)))
ctx := s.GetCtx()
gomock.InOrder(
s.store.
EXPECT().LoadOAuth2BlacklistedJTI(ctx, sig).
Return(nil, sql.ErrNoRows),
s.store.
EXPECT().SaveOAuth2BlacklistedJTI(ctx, model.OAuth2BlacklistedJTI{Signature: sig, ExpiresAt: assertion.ExpiresAt.Time}).
Return(nil),
)
client, err := s.provider.DefaultClientAuthenticationStrategy(s.GetCtx(), r, r.PostForm)
s.NoError(err)
s.Require().NotNil(client)
s.Equal("es384k", client.GetID())
}
func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnUnregisteredKeysES512() {
assertion := NewAssertion("es512", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0))
assertionJWT := jwt.NewWithClaims(jwt.SigningMethodES512, assertion)
token, err := assertionJWT.SignedString(MustParseECPrivateKey(exampleECP521PrivateKey))
token, err := assertionJWT.SignedString(keyECDSAP521)
s.Require().NoError(err)
s.Require().NotEqual("", token)
@ -668,6 +1183,40 @@ func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnUnregisteredKe
s.Nil(client)
}
func (s *ClientAuthenticationStrategySuite) TestShouldAuthKeysES512() {
assertion := NewAssertion("es512k", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0))
assertionJWT := jwt.NewWithClaims(jwt.SigningMethodES512, assertion)
assertionJWT.Header[oidc.JWTHeaderKeyIdentifier] = "es512"
token, err := assertionJWT.SignedString(keyECDSAP521)
s.Require().NoError(err)
s.Require().NotEqual("", token)
r := s.GetAssertionRequest(token)
sig := fmt.Sprintf("%x", sha256.Sum256([]byte(assertion.ID)))
ctx := s.GetCtx()
gomock.InOrder(
s.store.
EXPECT().LoadOAuth2BlacklistedJTI(ctx, sig).
Return(nil, sql.ErrNoRows),
s.store.
EXPECT().SaveOAuth2BlacklistedJTI(ctx, model.OAuth2BlacklistedJTI{Signature: sig, ExpiresAt: assertion.ExpiresAt.Time}).
Return(nil),
)
client, err := s.provider.DefaultClientAuthenticationStrategy(s.GetCtx(), r, r.PostForm)
s.NoError(err)
s.Require().NotNil(client)
s.Equal("es512k", client.GetID())
}
func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnJTIKnown() {
assertion := NewAssertion("hs512", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0))
@ -1384,126 +1933,3 @@ func NewAssertion(clientID string, tokenURL *url.URL, iat, exp time.Time) Regist
},
}
}
type RFC6749ErrorTest struct {
*fosite.RFC6749Error
}
func (err *RFC6749ErrorTest) Error() string {
return err.WithExposeDebug(true).GetDescription()
}
func ErrorToRFC6749ErrorTest(err error) (rfc error) {
if err == nil {
return nil
}
ferr := fosite.ErrorToRFC6749Error(err)
return &RFC6749ErrorTest{ferr}
}
func MustDecodeSecret(value string) *schema.PasswordDigest {
if secret, err := schema.DecodePasswordDigest(value); err != nil {
panic(err)
} else {
return secret
}
}
func MustParseRequestURI(input string) *url.URL {
if requestURI, err := url.ParseRequestURI(input); err != nil {
panic(err)
} else {
return requestURI
}
}
func MustParseRSAPrivateKey(data string) *rsa.PrivateKey {
block, _ := pem.Decode([]byte(data))
if block == nil || block.Bytes == nil || len(block.Bytes) == 0 {
panic("not pem encoded")
}
if block.Type != "RSA PRIVATE KEY" {
panic("not private key")
}
key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
panic(err)
}
return key
}
func MustParseECPrivateKey(data string) *ecdsa.PrivateKey {
block, _ := pem.Decode([]byte(data))
if block == nil || block.Bytes == nil || len(block.Bytes) == 0 {
panic("not pem encoded")
}
if block.Type != "EC PRIVATE KEY" {
panic("not private key")
}
key, err := x509.ParseECPrivateKey(block.Bytes)
if err != nil {
panic(err)
}
return key
}
const exampleRSAPrivateKey = `
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA60Vuz1N1wUHiCDIlbz8gE0dWPCmHEWnXKchEEISqIJ6j5Eah
Q/GwX3WK0UV5ATRvWhg6o7/WfrLYcAsi4w79TgMjJHLWIY/jzAS3quEtzOLlLSWZ
9FR9SomQm3T/ETOS8IvSGrksIj0WgX35jB1NnbqSTRnYx7Cg/TBJjmiaqd0b9G/8
LlReaihwGf8tvPgnteWIdon3EI2MKDBkaesRjpL98Cz7VvD7dajseAlUh9jQWVge
sN8qnm8pNPFAYsgxf//Jf0RfsND6H70zKKybDmyct4T4o/8qjivw4ly0XkArDCUj
Qx2KUF7nN+Bo9wwnNppjdnsOPUbus8o1a9vY1QIDAQABAoIBAQDl1SBY3PlN36SF
yScUtCALdUbi4taVxkVxBbioQlFIKHGGkRD9JN/dgSApK6r36FdXNhAi40cQ4nnZ
iqd8FKqTSTFNa/mPM9ee+ITMI8nwOz8SiYcKTndPF2/yzapXDYDgCFcpz/czQ2X2
/i+IFyA5k4dUVomVGhFLBZ71xW5BvGUBMUH0XkeR5+c4gLvgR209BlpBHlkX4tUQ
+RQoxbKpkntl0mjqf91zcOe4LJVsXZFyN+NVSzLEbGC3lVSSiyjVQH3s7ExnTaHi
PpwSoXzu5QJj5xRit/1B3/LEGpIlPGFrkhMzBDTN+HYV/VLbCHJzjg5GVJawA82E
h2BY6YWJAoGBAPmGaZL5ggnTVR2XVBLDKbwL/sesqiPZk45B+I5eObHl+v236JH9
RPMjdE10jOR1TzfQdmE2/RboKhiVn+osS+2W6VXSo7sMsSM1bLBPYhnwrNIqzrX8
Vgi2bCl2S8ZhVo2R8c5WUaD0Gpxs6hwPIMOQWWwxDlsbg/UoLrhD3X4XAoGBAPFg
VSvaWQdDVAqjM42ObhZtWxeLfEAcxRQDMQq7btrTwBZSrtP3S3Egu66cp/4PT4VD
Hc8tYyT2rNETiqT6b2Rm1MgeoJ8wRqte6ZXSQVVQUOd42VG04O3aaleAGhXjEkM2
avctRdKHDhQdIt+riPgaNj4FdYpmQ5zIrcZtBr/zAoGBAOBXzBX7xMHmwxEe3NUd
qSlMM579C9+9oF/3ymzeJMtgtcBmGHEhoFtmVgvJrV8+ZaIOCFExam2tASQnaqbV
etK7q0ChaNok+CJqxzThupcN/6PaHw4aOJQOx8KjfE95dqNEQ367txqaPk7D0dy2
cUPDRdLzbC/X1lWV8iNzyPGzAoGBAN4R2epRpYz4Fa7/vWNkAcaib6c2zmaR0YN6
+Di+ftvW6yfehDhBkWgQTHv2ZtxoK6oYOKmuQUP1qsNkbi8gtTEzJlrDStWKbcom
tVMAsNkT3otHdPEmL7bFNwcvtVAjrF6oBztHrLBnTr2UnMwZnhdczkC7dwuQ0G3D
d5VSI16fAoGAY7eeVDkic73GbZmtZibuodvPJ/z85RIBOrzf3ColO4jGI6Ej/EnD
rMEe/mRC27CJzS9L9Jc0Kt66mGSvodDGl0nBsXGNfPog0cGwweCVN0Eo2VJZbRTT
UoU05/Pvu2h3/E8gGTBY0/WPSo06YUsICjVDWNuOIa/7IY7SyE6Xxn0=
-----END RSA PRIVATE KEY-----`
const exampleECP256PrivateKey = `
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEID1fSsJ8qyEqj2DVkrshaNiXqaSDX7qViASRkyGGJFbEoAoGCCqGSM49
AwEHoUQDQgAENnBG+bBJIaIa+bRlHaLiXD86RAy+Ef9CVdAfpPGoNRfkOTcrrIV7
2wv3Y5e0he63Tn9iVAFYRFexK1mjFw7TfA==
-----END EC PRIVATE KEY-----`
const exampleECP384PrivateKey = `
-----BEGIN EC PRIVATE KEY-----
MIGkAgEBBDBPoOfapxtgZ8XNE7Wwdlw+9oDc6x4m57MITZyWzN62jkFUAYsvPJDF
9+g+e8CT5yqgBwYFK4EEACKhZANiAAQ2uZ0HIIxIavyjGyX13tIZVOaRB4+D64dF
s3DXDrpXcuDTSohw9xBW5sLDqRVu2LkBsCUFXtEJUHgC+O7wToNw8nh+KdDrcu/J
miNqbvEHuvlSlHWyx9HH8kAEuu1+SZg=
-----END EC PRIVATE KEY-----`
const exampleECP521PrivateKey = `
-----BEGIN EC PRIVATE KEY-----
MIHcAgEBBEIBT07AnitDd1Z01bl5W5VW8/vTWyu7w3MSqEmCeKcM19p/TAJAeS8L
6UOig2fTUeuMeA2PoOUjI2Bid927VsWcxE2gBwYFK4EEACOhgYkDgYYABAGnV9mu
xY0E7/k8b+glOOMaN0+Qt70H9OmSz6tC8tU3EayRwFlNPch9TlvEpbCS3MsDE9dN
78EpFx45MUqzzdZcOgAu+EUC9Zas1YVK+WMo0GFy+XtFq3kxubOclBb52M/63mcd
zZnA8aAu9iTK9YPfcw1YWTJliNdKUoxmGVV5Ca1W4w==
-----END EC PRIVATE KEY-----`

View File

@ -46,12 +46,22 @@ func NewClient(config schema.OpenIDConnectClientConfiguration) (client Client) {
base.ResponseModes = append(base.ResponseModes, fosite.ResponseModeType(mode))
}
if config.TokenEndpointAuthMethod != "" && config.TokenEndpointAuthMethod != "auto" {
client = &FullClient{
if config.TokenEndpointAuthMethod != "" || config.TokenEndpointAuthSigningAlg != "" ||
len(config.PublicKeys.Values) != 0 || config.PublicKeys.URI != nil || config.RequestObjectSigningAlg != "" {
full := &FullClient{
BaseClient: base,
TokenEndpointAuthMethod: config.TokenEndpointAuthMethod,
TokenEndpointAuthSigningAlgorithm: config.TokenEndpointAuthSigningAlg,
RequestObjectSigningAlgorithm: config.RequestObjectSigningAlg,
JSONWebKeys: NewPublicJSONWebKeySetFromSchemaJWK(config.PublicKeys.Values),
}
if config.PublicKeys.URI != nil {
full.JSONWebKeysURI = config.PublicKeys.URI.String()
}
client = full
} else {
client = base
}

View File

@ -1,4 +1,4 @@
package oidc
package oidc_test
import (
"fmt"
@ -14,30 +14,31 @@ import (
"github.com/authelia/authelia/v4/internal/authorization"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/model"
"github.com/authelia/authelia/v4/internal/oidc"
)
func TestNewClient(t *testing.T) {
config := schema.OpenIDConnectClientConfiguration{}
client := NewClient(config)
client := oidc.NewClient(config)
assert.Equal(t, "", client.GetID())
assert.Equal(t, "", client.GetDescription())
assert.Len(t, client.GetResponseModes(), 0)
assert.Len(t, client.GetResponseTypes(), 1)
assert.Equal(t, "", client.GetSectorIdentifier())
bclient, ok := client.(*BaseClient)
bclient, ok := client.(*oidc.BaseClient)
require.True(t, ok)
assert.Equal(t, "", bclient.UserinfoSigningAlg)
assert.Equal(t, SigningAlgNone, client.GetUserinfoSigningAlg())
assert.Equal(t, oidc.SigningAlgNone, client.GetUserinfoSigningAlg())
_, ok = client.(*FullClient)
_, ok = client.(*oidc.FullClient)
assert.False(t, ok)
config = schema.OpenIDConnectClientConfiguration{
ID: myclient,
Description: myclientdesc,
Policy: twofactor,
Secret: MustDecodeSecret(badsecret),
Secret: tOpenIDConnectPlainTextClientSecret,
RedirectURIs: []string{examplecom},
Scopes: schema.DefaultOpenIDConnectClientConfiguration.Scopes,
ResponseTypes: schema.DefaultOpenIDConnectClientConfiguration.ResponseTypes,
@ -45,44 +46,42 @@ func TestNewClient(t *testing.T) {
ResponseModes: schema.DefaultOpenIDConnectClientConfiguration.ResponseModes,
}
client = NewClient(config)
client = oidc.NewClient(config)
assert.Equal(t, myclient, client.GetID())
require.Len(t, client.GetResponseModes(), 1)
assert.Equal(t, fosite.ResponseModeFormPost, client.GetResponseModes()[0])
assert.Equal(t, authorization.TwoFactor, client.GetAuthorizationPolicy())
config = schema.OpenIDConnectClientConfiguration{
TokenEndpointAuthMethod: ClientAuthMethodClientSecretPost,
TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretPost,
}
client = NewClient(config)
client = oidc.NewClient(config)
fclient, ok := client.(*FullClient)
var niljwks *jose.JSONWebKeySet
fclient, ok := client.(*oidc.FullClient)
require.True(t, ok)
assert.Equal(t, "", fclient.UserinfoSigningAlg)
assert.Equal(t, SigningAlgNone, client.GetUserinfoSigningAlg())
assert.Equal(t, SigningAlgNone, fclient.UserinfoSigningAlg)
assert.Equal(t, oidc.SigningAlgNone, client.GetUserinfoSigningAlg())
assert.Equal(t, oidc.SigningAlgNone, fclient.UserinfoSigningAlg)
assert.Equal(t, "", fclient.IDTokenSigningAlg)
assert.Equal(t, SigningAlgRSAUsingSHA256, client.GetIDTokenSigningAlg())
assert.Equal(t, SigningAlgRSAUsingSHA256, fclient.IDTokenSigningAlg)
assert.Equal(t, oidc.SigningAlgRSAUsingSHA256, client.GetIDTokenSigningAlg())
assert.Equal(t, oidc.SigningAlgRSAUsingSHA256, fclient.IDTokenSigningAlg)
assert.Equal(t, ClientAuthMethodClientSecretPost, fclient.TokenEndpointAuthMethod)
assert.Equal(t, ClientAuthMethodClientSecretPost, fclient.GetTokenEndpointAuthMethod())
assert.Equal(t, oidc.ClientAuthMethodClientSecretPost, fclient.TokenEndpointAuthMethod)
assert.Equal(t, oidc.ClientAuthMethodClientSecretPost, fclient.GetTokenEndpointAuthMethod())
assert.Equal(t, "", fclient.TokenEndpointAuthSigningAlgorithm)
assert.Equal(t, SigningAlgRSAUsingSHA256, fclient.GetTokenEndpointAuthSigningAlgorithm())
assert.Equal(t, SigningAlgRSAUsingSHA256, fclient.TokenEndpointAuthSigningAlgorithm)
assert.Equal(t, oidc.SigningAlgRSAUsingSHA256, fclient.GetTokenEndpointAuthSigningAlgorithm())
assert.Equal(t, oidc.SigningAlgRSAUsingSHA256, fclient.TokenEndpointAuthSigningAlgorithm)
assert.Equal(t, "", fclient.RequestObjectSigningAlgorithm)
assert.Equal(t, "", fclient.GetRequestObjectSigningAlgorithm())
fclient.RequestObjectSigningAlgorithm = SigningAlgRSAUsingSHA256
assert.Equal(t, SigningAlgRSAUsingSHA256, fclient.GetRequestObjectSigningAlgorithm())
fclient.RequestObjectSigningAlgorithm = oidc.SigningAlgRSAUsingSHA256
assert.Equal(t, oidc.SigningAlgRSAUsingSHA256, fclient.GetRequestObjectSigningAlgorithm())
assert.Equal(t, "", fclient.JSONWebKeysURI)
assert.Equal(t, "", fclient.GetJSONWebKeysURI())
@ -90,22 +89,24 @@ func TestNewClient(t *testing.T) {
fclient.JSONWebKeysURI = "https://example.com"
assert.Equal(t, "https://example.com", fclient.GetJSONWebKeysURI())
var niljwks *jose.JSONWebKeySet
assert.Equal(t, niljwks, fclient.JSONWebKeys)
assert.Equal(t, niljwks, fclient.GetJSONWebKeys())
assert.Equal(t, ClientConsentMode(0), fclient.Consent.Mode)
assert.Equal(t, oidc.ClientConsentMode(0), fclient.Consent.Mode)
assert.Equal(t, time.Second*0, fclient.Consent.Duration)
assert.Equal(t, ClientConsent{Mode: ClientConsentModeExplicit}, fclient.GetConsentPolicy())
assert.Equal(t, oidc.ClientConsent{Mode: oidc.ClientConsentModeExplicit}, fclient.GetConsentPolicy())
fclient.TokenEndpointAuthMethod = ""
fclient.Public = false
assert.Equal(t, ClientAuthMethodClientSecretBasic, fclient.GetTokenEndpointAuthMethod())
assert.Equal(t, ClientAuthMethodClientSecretBasic, fclient.TokenEndpointAuthMethod)
assert.Equal(t, oidc.ClientAuthMethodClientSecretBasic, fclient.GetTokenEndpointAuthMethod())
assert.Equal(t, oidc.ClientAuthMethodClientSecretBasic, fclient.TokenEndpointAuthMethod)
fclient.TokenEndpointAuthMethod = ""
fclient.Public = true
assert.Equal(t, ClientAuthMethodNone, fclient.GetTokenEndpointAuthMethod())
assert.Equal(t, ClientAuthMethodNone, fclient.TokenEndpointAuthMethod)
assert.Equal(t, oidc.ClientAuthMethodNone, fclient.GetTokenEndpointAuthMethod())
assert.Equal(t, oidc.ClientAuthMethodNone, fclient.TokenEndpointAuthMethod)
assert.Equal(t, []string(nil), fclient.RequestURIs)
assert.Equal(t, []string(nil), fclient.GetRequestURIs())
@ -114,13 +115,13 @@ func TestNewClient(t *testing.T) {
func TestBaseClient_ValidatePARPolicy(t *testing.T) {
testCases := []struct {
name string
client *BaseClient
client *oidc.BaseClient
have *fosite.Request
expected string
}{
{
"ShouldNotEnforcePAR",
&BaseClient{
&oidc.BaseClient{
EnforcePAR: false,
},
&fosite.Request{},
@ -128,36 +129,36 @@ func TestBaseClient_ValidatePARPolicy(t *testing.T) {
},
{
"ShouldEnforcePARAndErrorWithoutCorrectRequestURI",
&BaseClient{
&oidc.BaseClient{
EnforcePAR: true,
},
&fosite.Request{
Form: map[string][]string{
FormParameterRequestURI: {"https://google.com"},
oidc.FormParameterRequestURI: {"https://google.com"},
},
},
"invalid_request",
},
{
"ShouldEnforcePARAndErrorWithEmptyRequestURI",
&BaseClient{
&oidc.BaseClient{
EnforcePAR: true,
},
&fosite.Request{
Form: map[string][]string{
FormParameterRequestURI: {""},
oidc.FormParameterRequestURI: {""},
},
},
"invalid_request",
},
{
"ShouldEnforcePARAndNotErrorWithCorrectRequestURI",
&BaseClient{
&oidc.BaseClient{
EnforcePAR: true,
},
&fosite.Request{
Form: map[string][]string{
FormParameterRequestURI: {urnPARPrefix + "abc"},
oidc.FormParameterRequestURI: {oidc.RedirectURIPrefixPushedAuthorizationRequestURN + "abc"},
},
},
"",
@ -166,7 +167,7 @@ func TestBaseClient_ValidatePARPolicy(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := tc.client.ValidatePARPolicy(tc.have, urnPARPrefix)
err := tc.client.ValidatePARPolicy(tc.have, oidc.RedirectURIPrefixPushedAuthorizationRequestURN)
switch tc.expected {
case "":
@ -179,7 +180,7 @@ func TestBaseClient_ValidatePARPolicy(t *testing.T) {
}
func TestIsAuthenticationLevelSufficient(t *testing.T) {
c := &FullClient{BaseClient: &BaseClient{}}
c := &oidc.FullClient{BaseClient: &oidc.BaseClient{}}
c.Policy = authorization.Bypass
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.NotAuthenticated))
@ -203,7 +204,7 @@ func TestIsAuthenticationLevelSufficient(t *testing.T) {
}
func TestClient_GetConsentResponseBody(t *testing.T) {
c := &FullClient{BaseClient: &BaseClient{}}
c := &oidc.FullClient{BaseClient: &oidc.BaseClient{}}
consentRequestBody := c.GetConsentResponseBody(nil)
assert.Equal(t, "", consentRequestBody.ClientID)
@ -216,10 +217,10 @@ func TestClient_GetConsentResponseBody(t *testing.T) {
consent := &model.OAuth2ConsentSession{
RequestedAudience: []string{examplecom},
RequestedScopes: []string{ScopeOpenID, ScopeGroups},
RequestedScopes: []string{oidc.ScopeOpenID, oidc.ScopeGroups},
}
expectedScopes := []string{ScopeOpenID, ScopeGroups}
expectedScopes := []string{oidc.ScopeOpenID, oidc.ScopeGroups}
expectedAudiences := []string{examplecom}
consentRequestBody = c.GetConsentResponseBody(consent)
@ -230,7 +231,7 @@ func TestClient_GetConsentResponseBody(t *testing.T) {
}
func TestClient_GetAudience(t *testing.T) {
c := &FullClient{BaseClient: &BaseClient{}}
c := &oidc.FullClient{BaseClient: &oidc.BaseClient{}}
audience := c.GetAudience()
assert.Len(t, audience, 0)
@ -243,24 +244,24 @@ func TestClient_GetAudience(t *testing.T) {
}
func TestClient_GetScopes(t *testing.T) {
c := &FullClient{BaseClient: &BaseClient{}}
c := &oidc.FullClient{BaseClient: &oidc.BaseClient{}}
scopes := c.GetScopes()
assert.Len(t, scopes, 0)
c.Scopes = []string{ScopeOpenID}
c.Scopes = []string{oidc.ScopeOpenID}
scopes = c.GetScopes()
require.Len(t, scopes, 1)
assert.Equal(t, ScopeOpenID, scopes[0])
assert.Equal(t, oidc.ScopeOpenID, scopes[0])
}
func TestClient_GetGrantTypes(t *testing.T) {
c := &FullClient{BaseClient: &BaseClient{}}
c := &oidc.FullClient{BaseClient: &oidc.BaseClient{}}
grantTypes := c.GetGrantTypes()
require.Len(t, grantTypes, 1)
assert.Equal(t, GrantTypeAuthorizationCode, grantTypes[0])
assert.Equal(t, oidc.GrantTypeAuthorizationCode, grantTypes[0])
c.GrantTypes = []string{"device_code"}
@ -270,30 +271,30 @@ func TestClient_GetGrantTypes(t *testing.T) {
}
func TestClient_Hashing(t *testing.T) {
c := &FullClient{BaseClient: &BaseClient{}}
c := &oidc.FullClient{BaseClient: &oidc.BaseClient{}}
hashedSecret := c.GetHashedSecret()
assert.Equal(t, []byte(nil), hashedSecret)
c.Secret = MustDecodeSecret(badsecret)
c.Secret = tOpenIDConnectPlainTextClientSecret
assert.True(t, c.Secret.MatchBytes([]byte("a_bad_secret")))
assert.True(t, c.Secret.MatchBytes([]byte("client-secret")))
}
func TestClient_GetHashedSecret(t *testing.T) {
c := &FullClient{BaseClient: &BaseClient{}}
c := &oidc.FullClient{BaseClient: &oidc.BaseClient{}}
hashedSecret := c.GetHashedSecret()
assert.Equal(t, []byte(nil), hashedSecret)
c.Secret = MustDecodeSecret(badsecret)
c.Secret = tOpenIDConnectPlainTextClientSecret
hashedSecret = c.GetHashedSecret()
assert.Equal(t, []byte(badsecret), hashedSecret)
assert.Equal(t, []byte("$plaintext$client-secret"), hashedSecret)
}
func TestClient_GetID(t *testing.T) {
c := &FullClient{BaseClient: &BaseClient{}}
c := &oidc.FullClient{BaseClient: &oidc.BaseClient{}}
id := c.GetID()
assert.Equal(t, "", id)
@ -305,7 +306,7 @@ func TestClient_GetID(t *testing.T) {
}
func TestClient_GetRedirectURIs(t *testing.T) {
c := &FullClient{BaseClient: &BaseClient{}}
c := &oidc.FullClient{BaseClient: &oidc.BaseClient{}}
redirectURIs := c.GetRedirectURIs()
require.Len(t, redirectURIs, 0)
@ -318,7 +319,7 @@ func TestClient_GetRedirectURIs(t *testing.T) {
}
func TestClient_GetResponseModes(t *testing.T) {
c := &FullClient{BaseClient: &BaseClient{}}
c := &oidc.FullClient{BaseClient: &oidc.BaseClient{}}
responseModes := c.GetResponseModes()
require.Len(t, responseModes, 0)
@ -337,18 +338,18 @@ func TestClient_GetResponseModes(t *testing.T) {
}
func TestClient_GetResponseTypes(t *testing.T) {
c := &FullClient{BaseClient: &BaseClient{}}
c := &oidc.FullClient{BaseClient: &oidc.BaseClient{}}
responseTypes := c.GetResponseTypes()
require.Len(t, responseTypes, 1)
assert.Equal(t, ResponseTypeAuthorizationCodeFlow, responseTypes[0])
assert.Equal(t, oidc.ResponseTypeAuthorizationCodeFlow, responseTypes[0])
c.ResponseTypes = []string{ResponseTypeAuthorizationCodeFlow, ResponseTypeImplicitFlowIDToken}
c.ResponseTypes = []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeImplicitFlowIDToken}
responseTypes = c.GetResponseTypes()
require.Len(t, responseTypes, 2)
assert.Equal(t, ResponseTypeAuthorizationCodeFlow, responseTypes[0])
assert.Equal(t, ResponseTypeImplicitFlowIDToken, responseTypes[1])
assert.Equal(t, oidc.ResponseTypeAuthorizationCodeFlow, responseTypes[0])
assert.Equal(t, oidc.ResponseTypeImplicitFlowIDToken, responseTypes[1])
}
func TestNewClientPKCE(t *testing.T) {
@ -423,7 +424,7 @@ func TestNewClientPKCE(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
client := NewClient(tc.have)
client := oidc.NewClient(tc.have)
assert.Equal(t, tc.expectedEnforcePKCE, client.GetPKCEEnforcement())
assert.Equal(t, tc.expectedEnforcePKCEChallengeMethod, client.GetPKCEChallengeMethodEnforcement())
@ -473,14 +474,14 @@ func TestNewClientPAR(t *testing.T) {
"ShouldEnforcePARAndErrorOnNonPARRequest",
schema.OpenIDConnectClientConfiguration{EnforcePAR: true},
true,
&fosite.Request{Form: map[string][]string{FormParameterRequestURI: {"https://example.com"}}},
&fosite.Request{Form: map[string][]string{oidc.FormParameterRequestURI: {"https://example.com"}}},
"invalid_request",
"The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Pushed Authorization Requests are enforced for this client but no such request was sent. The request_uri parameter 'https://example.com' is malformed."},
{
"ShouldEnforcePARAndNotErrorOnPARRequest",
schema.OpenIDConnectClientConfiguration{EnforcePAR: true},
true,
&fosite.Request{Form: map[string][]string{FormParameterRequestURI: {fmt.Sprintf("%sabc", urnPARPrefix)}}},
&fosite.Request{Form: map[string][]string{oidc.FormParameterRequestURI: {fmt.Sprintf("%sabc", oidc.RedirectURIPrefixPushedAuthorizationRequestURN)}}},
"",
"",
},
@ -488,12 +489,12 @@ func TestNewClientPAR(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
client := NewClient(tc.have)
client := oidc.NewClient(tc.have)
assert.Equal(t, tc.expected, client.GetPAREnforcement())
if tc.r != nil {
err := client.ValidatePARPolicy(tc.r, urnPARPrefix)
err := client.ValidatePARPolicy(tc.r, oidc.RedirectURIPrefixPushedAuthorizationRequestURN)
if tc.err != "" {
require.NotNil(t, err)
@ -518,25 +519,25 @@ func TestNewClientResponseModes(t *testing.T) {
}{
{
"ShouldEnforceResponseModePolicyAndAllowDefaultModeQuery",
schema.OpenIDConnectClientConfiguration{ResponseModes: []string{ResponseModeQuery}},
schema.OpenIDConnectClientConfiguration{ResponseModes: []string{oidc.ResponseModeQuery}},
[]fosite.ResponseModeType{fosite.ResponseModeQuery},
&fosite.AuthorizeRequest{DefaultResponseMode: fosite.ResponseModeQuery, ResponseMode: fosite.ResponseModeDefault, Request: fosite.Request{Form: map[string][]string{FormParameterResponseMode: nil}}},
&fosite.AuthorizeRequest{DefaultResponseMode: fosite.ResponseModeQuery, ResponseMode: fosite.ResponseModeDefault, Request: fosite.Request{Form: map[string][]string{oidc.FormParameterResponseMode: nil}}},
"",
"",
},
{
"ShouldEnforceResponseModePolicyAndFailOnDefaultMode",
schema.OpenIDConnectClientConfiguration{ResponseModes: []string{ResponseModeFormPost}},
schema.OpenIDConnectClientConfiguration{ResponseModes: []string{oidc.ResponseModeFormPost}},
[]fosite.ResponseModeType{fosite.ResponseModeFormPost},
&fosite.AuthorizeRequest{DefaultResponseMode: fosite.ResponseModeQuery, ResponseMode: fosite.ResponseModeDefault, Request: fosite.Request{Form: map[string][]string{FormParameterResponseMode: nil}}},
&fosite.AuthorizeRequest{DefaultResponseMode: fosite.ResponseModeQuery, ResponseMode: fosite.ResponseModeDefault, Request: fosite.Request{Form: map[string][]string{oidc.FormParameterResponseMode: nil}}},
"unsupported_response_mode",
"The authorization server does not support obtaining a response using this response mode. The request omitted the response_mode making the default response_mode 'query' based on the other authorization request parameters but registered OAuth 2.0 client doesn't support this response_mode",
},
{
"ShouldNotEnforceConfiguredResponseMode",
schema.OpenIDConnectClientConfiguration{ResponseModes: []string{ResponseModeFormPost}},
schema.OpenIDConnectClientConfiguration{ResponseModes: []string{oidc.ResponseModeFormPost}},
[]fosite.ResponseModeType{fosite.ResponseModeFormPost},
&fosite.AuthorizeRequest{DefaultResponseMode: fosite.ResponseModeQuery, ResponseMode: fosite.ResponseModeQuery, Request: fosite.Request{Form: map[string][]string{FormParameterResponseMode: {ResponseModeQuery}}}},
&fosite.AuthorizeRequest{DefaultResponseMode: fosite.ResponseModeQuery, ResponseMode: fosite.ResponseModeQuery, Request: fosite.Request{Form: map[string][]string{oidc.FormParameterResponseMode: {oidc.ResponseModeQuery}}}},
"",
"",
},
@ -544,7 +545,7 @@ func TestNewClientResponseModes(t *testing.T) {
"ShouldNotEnforceUnconfiguredResponseMode",
schema.OpenIDConnectClientConfiguration{ResponseModes: []string{}},
[]fosite.ResponseModeType{},
&fosite.AuthorizeRequest{DefaultResponseMode: fosite.ResponseModeQuery, ResponseMode: fosite.ResponseModeDefault, Request: fosite.Request{Form: map[string][]string{FormParameterResponseMode: {ResponseModeQuery}}}},
&fosite.AuthorizeRequest{DefaultResponseMode: fosite.ResponseModeQuery, ResponseMode: fosite.ResponseModeDefault, Request: fosite.Request{Form: map[string][]string{oidc.FormParameterResponseMode: {oidc.ResponseModeQuery}}}},
"",
"",
},
@ -552,7 +553,7 @@ func TestNewClientResponseModes(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
client := NewClient(tc.have)
client := oidc.NewClient(tc.have)
assert.Equal(t, tc.expected, client.GetResponseModes())
@ -572,10 +573,48 @@ func TestNewClientResponseModes(t *testing.T) {
}
func TestClient_IsPublic(t *testing.T) {
c := &FullClient{BaseClient: &BaseClient{}}
c := &oidc.FullClient{BaseClient: &oidc.BaseClient{}}
assert.False(t, c.IsPublic())
c.Public = true
assert.True(t, c.IsPublic())
}
func TestNewClient_JSONWebKeySetURI(t *testing.T) {
var (
client oidc.Client
clientf *oidc.FullClient
ok bool
)
client = oidc.NewClient(schema.OpenIDConnectClientConfiguration{
TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretPost,
PublicKeys: schema.OpenIDConnectClientPublicKeys{
URI: MustParseRequestURI("https://google.com"),
},
})
require.NotNil(t, client)
clientf, ok = client.(*oidc.FullClient)
require.True(t, ok)
assert.Equal(t, "https://google.com", clientf.GetJSONWebKeysURI())
client = oidc.NewClient(schema.OpenIDConnectClientConfiguration{
TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretPost,
PublicKeys: schema.OpenIDConnectClientPublicKeys{
URI: nil,
},
})
require.NotNil(t, client)
clientf, ok = client.(*oidc.FullClient)
require.True(t, ok)
assert.Equal(t, "", clientf.GetJSONWebKeysURI())
}

View File

@ -6,7 +6,6 @@ import (
"hash"
"html/template"
"net/url"
"path"
"time"
"github.com/hashicorp/go-retryablehttp"
@ -43,7 +42,7 @@ func NewConfig(config *schema.OpenIDConnectConfiguration, templates *templates.P
PAR: PARConfig{
Enforced: config.PAR.Enforce,
ContextLifespan: config.PAR.ContextLifespan,
URIPrefix: urnPARPrefix,
URIPrefix: RedirectURIPrefixPushedAuthorizationRequestURN,
},
Templates: templates,
}
@ -90,9 +89,8 @@ type Config struct {
AllowedPrompts []string
RefreshTokenScopes []string
HTTPClient *retryablehttp.Client
FormPostHTMLTemplate *template.Template
MessageCatalog i18n.MessageCatalog
HTTPClient *retryablehttp.Client
MessageCatalog i18n.MessageCatalog
Templates *templates.Provider
}
@ -386,8 +384,8 @@ func (c *Config) GetDisableRefreshTokenValidation(ctx context.Context) (disable
// GetAuthorizeCodeLifespan returns the authorization code lifespan.
func (c *Config) GetAuthorizeCodeLifespan(ctx context.Context) (lifespan time.Duration) {
if c.Lifespans.AuthorizeCode <= 0 {
c.Lifespans.AccessToken = lifespanAuthorizeCodeDefault
if c.Lifespans.AuthorizeCode.Seconds() <= 0 {
c.Lifespans.AuthorizeCode = lifespanAuthorizeCodeDefault
}
return c.Lifespans.AuthorizeCode
@ -395,8 +393,8 @@ func (c *Config) GetAuthorizeCodeLifespan(ctx context.Context) (lifespan time.Du
// GetRefreshTokenLifespan returns the refresh token lifespan.
func (c *Config) GetRefreshTokenLifespan(ctx context.Context) (lifespan time.Duration) {
if c.Lifespans.RefreshToken <= 0 {
c.Lifespans.AccessToken = lifespanRefreshTokenDefault
if c.Lifespans.RefreshToken.Seconds() <= 0 {
c.Lifespans.RefreshToken = lifespanRefreshTokenDefault
}
return c.Lifespans.RefreshToken
@ -404,8 +402,8 @@ func (c *Config) GetRefreshTokenLifespan(ctx context.Context) (lifespan time.Dur
// GetIDTokenLifespan returns the ID token lifespan.
func (c *Config) GetIDTokenLifespan(ctx context.Context) (lifespan time.Duration) {
if c.Lifespans.IDToken <= 0 {
c.Lifespans.AccessToken = lifespanTokenDefault
if c.Lifespans.IDToken.Seconds() <= 0 {
c.Lifespans.IDToken = lifespanTokenDefault
}
return c.Lifespans.IDToken
@ -413,7 +411,7 @@ func (c *Config) GetIDTokenLifespan(ctx context.Context) (lifespan time.Duration
// GetAccessTokenLifespan returns the access token lifespan.
func (c *Config) GetAccessTokenLifespan(ctx context.Context) (lifespan time.Duration) {
if c.Lifespans.AccessToken <= 0 {
if c.Lifespans.AccessToken.Seconds() <= 0 {
c.Lifespans.AccessToken = lifespanTokenDefault
}
@ -528,15 +526,13 @@ func (c *Config) GetFormPostHTMLTemplate(ctx context.Context) (tmpl *template.Te
// GetTokenURL returns the token URL.
func (c *Config) GetTokenURL(ctx context.Context) (tokenURL string) {
if ctx, ok := ctx.(OpenIDConnectContext); ok {
tokenURI, err := ctx.IssuerURL()
if err != nil {
if octx, ok := ctx.(OpenIDConnectContext); ok {
switch issuerURL, err := octx.IssuerURL(); err {
case nil:
return issuerURL.JoinPath(EndpointPathToken).String()
default:
return c.TokenURL
}
tokenURI.Path = path.Join(tokenURI.Path, EndpointPathToken)
return tokenURI.String()
}
return c.TokenURL
@ -592,7 +588,7 @@ func (c *Config) GetResponseModeHandlerExtension(ctx context.Context) (handler f
// usually 'urn:ietf:params:oauth:request_uri:'.
func (c *Config) GetPushedAuthorizeRequestURIPrefix(ctx context.Context) string {
if c.PAR.URIPrefix == "" {
c.PAR.URIPrefix = urnPARPrefix
c.PAR.URIPrefix = RedirectURIPrefixPushedAuthorizationRequestURN
}
return c.PAR.URIPrefix
@ -607,7 +603,7 @@ func (c *Config) EnforcePushedAuthorize(ctx context.Context) bool {
// GetPushedAuthorizeContextLifespan is the lifespan of the short-lived PAR context.
func (c *Config) GetPushedAuthorizeContextLifespan(ctx context.Context) (lifespan time.Duration) {
if c.PAR.ContextLifespan.Seconds() == 0 {
if c.PAR.ContextLifespan.Seconds() <= 0 {
c.PAR.ContextLifespan = lifespanPARContextDefault
}

View File

@ -0,0 +1,229 @@
package oidc_test
import (
"context"
"fmt"
"net/url"
"testing"
"time"
"github.com/ory/fosite/token/jwt"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/authelia/authelia/v4/internal/oidc"
"github.com/authelia/authelia/v4/internal/templates"
)
func TestConfig_GetAllowedPrompts(t *testing.T) {
ctx := context.Background()
config := &oidc.Config{}
assert.Equal(t, []string(nil), config.AllowedPrompts)
assert.Equal(t, []string{oidc.PromptNone, oidc.PromptLogin, oidc.PromptConsent}, config.GetAllowedPrompts(ctx))
assert.Equal(t, []string{oidc.PromptNone, oidc.PromptLogin, oidc.PromptConsent}, config.AllowedPrompts)
config.AllowedPrompts = []string{oidc.PromptNone}
assert.Equal(t, []string{oidc.PromptNone}, config.AllowedPrompts)
}
func TestConfig_PKCE(t *testing.T) {
ctx := context.Background()
config := &oidc.Config{}
assert.False(t, config.GetEnforcePKCE(ctx))
assert.False(t, config.GetEnforcePKCEForPublicClients(ctx))
config.ProofKeyCodeExchange.Enforce = true
assert.True(t, config.GetEnforcePKCE(ctx))
assert.True(t, config.GetEnforcePKCEForPublicClients(ctx))
config.ProofKeyCodeExchange.Enforce = false
assert.False(t, config.GetEnforcePKCEForPublicClients(ctx))
config.ProofKeyCodeExchange.EnforcePublicClients = true
assert.True(t, config.GetEnforcePKCEForPublicClients(ctx))
assert.False(t, config.GetEnablePKCEPlainChallengeMethod(ctx))
config.ProofKeyCodeExchange.AllowPlainChallengeMethod = true
assert.True(t, config.GetEnablePKCEPlainChallengeMethod(ctx))
}
func TestConfig_GrantTypeJWTBearer(t *testing.T) {
ctx := context.Background()
config := &oidc.Config{}
assert.False(t, config.GetGrantTypeJWTBearerIDOptional(ctx))
assert.False(t, config.GetGrantTypeJWTBearerCanSkipClientAuth(ctx))
assert.False(t, config.GetGrantTypeJWTBearerIssuedDateOptional(ctx))
config.GrantTypeJWTBearer.OptionalJTIClaim = true
assert.True(t, config.GetGrantTypeJWTBearerIDOptional(ctx))
assert.False(t, config.GetGrantTypeJWTBearerCanSkipClientAuth(ctx))
assert.False(t, config.GetGrantTypeJWTBearerIssuedDateOptional(ctx))
config.GrantTypeJWTBearer.OptionalClientAuth = true
assert.True(t, config.GetGrantTypeJWTBearerIDOptional(ctx))
assert.True(t, config.GetGrantTypeJWTBearerCanSkipClientAuth(ctx))
assert.False(t, config.GetGrantTypeJWTBearerIssuedDateOptional(ctx))
config.GrantTypeJWTBearer.OptionalIssuedDate = true
assert.True(t, config.GetGrantTypeJWTBearerIDOptional(ctx))
assert.True(t, config.GetGrantTypeJWTBearerCanSkipClientAuth(ctx))
assert.True(t, config.GetGrantTypeJWTBearerIssuedDateOptional(ctx))
}
func TestConfig_Durations(t *testing.T) {
ctx := context.Background()
config := &oidc.Config{}
assert.Equal(t, time.Duration(0), config.JWTMaxDuration)
assert.Equal(t, time.Hour*24, config.GetJWTMaxDuration(ctx))
assert.Equal(t, time.Hour*24, config.JWTMaxDuration)
assert.Equal(t, time.Duration(0), config.Lifespans.IDToken)
assert.Equal(t, time.Hour, config.GetIDTokenLifespan(ctx))
assert.Equal(t, time.Hour, config.Lifespans.IDToken)
assert.Equal(t, time.Duration(0), config.Lifespans.AccessToken)
assert.Equal(t, time.Hour, config.GetAccessTokenLifespan(ctx))
assert.Equal(t, time.Hour, config.Lifespans.AccessToken)
assert.Equal(t, time.Duration(0), config.Lifespans.RefreshToken)
assert.Equal(t, time.Hour*24*30, config.GetRefreshTokenLifespan(ctx))
assert.Equal(t, time.Hour*24*30, config.Lifespans.RefreshToken)
assert.Equal(t, time.Duration(0), config.Lifespans.AuthorizeCode)
assert.Equal(t, time.Minute*15, config.GetAuthorizeCodeLifespan(ctx))
assert.Equal(t, time.Minute*15, config.Lifespans.AuthorizeCode)
}
func TestConfig_GetTokenEntropy(t *testing.T) {
ctx := context.Background()
config := &oidc.Config{}
assert.Equal(t, 0, config.TokenEntropy)
assert.Equal(t, 32, config.GetTokenEntropy(ctx))
assert.Equal(t, 32, config.TokenEntropy)
}
func TestConfig_Misc(t *testing.T) {
ctx := context.Background()
config := &oidc.Config{}
assert.False(t, config.DisableRefreshTokenValidation)
assert.False(t, config.GetDisableRefreshTokenValidation(ctx))
assert.Equal(t, "", config.Issuers.AccessToken)
assert.Equal(t, "", config.GetAccessTokenIssuer(ctx))
assert.Equal(t, "", config.Issuers.IDToken)
assert.Equal(t, "", config.GetIDTokenIssuer(ctx))
assert.Equal(t, jwt.JWTScopeFieldUnset, config.JWTScopeField)
assert.Equal(t, jwt.JWTScopeFieldList, config.GetJWTScopeField(ctx))
assert.Equal(t, jwt.JWTScopeFieldList, config.JWTScopeField)
assert.Equal(t, []string(nil), config.SanitationWhiteList)
assert.Equal(t, []string(nil), config.GetSanitationWhiteList(ctx))
assert.Equal(t, []string(nil), config.SanitationWhiteList)
assert.False(t, config.OmitRedirectScopeParameter)
assert.False(t, config.GetOmitRedirectScopeParam(ctx))
assert.NotNil(t, config.GetRedirectSecureChecker(ctx))
assert.NotNil(t, config.GetHTTPClient(ctx))
assert.Nil(t, config.Strategy.Scope)
assert.NotNil(t, config.GetScopeStrategy(ctx))
assert.NotNil(t, config.Strategy.Scope)
assert.Nil(t, config.Strategy.Audience)
assert.NotNil(t, config.GetAudienceStrategy(ctx))
assert.NotNil(t, config.Strategy.Audience)
assert.Equal(t, []string(nil), config.RefreshTokenScopes)
assert.Equal(t, []string{oidc.ScopeOffline, oidc.ScopeOfflineAccess}, config.GetRefreshTokenScopes(ctx))
assert.Equal(t, []string{oidc.ScopeOffline, oidc.ScopeOfflineAccess}, config.RefreshTokenScopes)
assert.Equal(t, 0, config.MinParameterEntropy)
assert.Equal(t, 8, config.GetMinParameterEntropy(ctx))
assert.Equal(t, 8, config.MinParameterEntropy)
assert.False(t, config.SendDebugMessagesToClients)
assert.False(t, config.GetSendDebugMessagesToClients(ctx))
config.SendDebugMessagesToClients = true
assert.True(t, config.GetSendDebugMessagesToClients(ctx))
assert.Nil(t, config.Strategy.JWKSFetcher)
assert.NotNil(t, config.GetJWKSFetcherStrategy(ctx))
assert.NotNil(t, config.Strategy.JWKSFetcher)
assert.Nil(t, config.Strategy.ClientAuthentication)
assert.Nil(t, config.GetClientAuthenticationStrategy(ctx))
assert.Nil(t, config.MessageCatalog)
assert.Nil(t, config.GetMessageCatalog(ctx))
assert.Nil(t, config.Templates)
assert.Nil(t, config.GetFormPostHTMLTemplate(ctx))
var err error
config.Templates, err = templates.New(templates.Config{})
require.NoError(t, err)
assert.NotNil(t, config.GetFormPostHTMLTemplate(ctx))
assert.NotNil(t, config.Templates)
assert.False(t, config.GetUseLegacyErrorFormat(ctx))
assert.Nil(t, config.GetAuthorizeEndpointHandlers(ctx))
assert.Nil(t, config.GetTokenEndpointHandlers(ctx))
assert.Nil(t, config.GetTokenIntrospectionHandlers(ctx))
assert.Nil(t, config.GetRevocationHandlers(ctx))
assert.Nil(t, config.GetPushedAuthorizeEndpointHandlers(ctx))
assert.Nil(t, config.GetResponseModeHandlerExtension(ctx))
assert.Equal(t, "", config.GetTokenURL(ctx))
octx := &MockOpenIDConnectContext{
Context: ctx,
IssuerURLFunc: func() (issuerURL *url.URL, err error) {
return nil, fmt.Errorf("test error")
},
}
assert.Equal(t, "", config.GetTokenURL(octx))
}
func TestConfig_PAR(t *testing.T) {
ctx := context.Background()
config := &oidc.Config{}
assert.Equal(t, "", config.PAR.URIPrefix)
assert.Equal(t, "urn:ietf:params:oauth:request_uri:", config.GetPushedAuthorizeRequestURIPrefix(ctx))
assert.Equal(t, "urn:ietf:params:oauth:request_uri:", config.PAR.URIPrefix)
assert.False(t, config.PAR.Enforced)
assert.False(t, config.EnforcePushedAuthorize(ctx))
assert.False(t, config.PAR.Enforced)
config.PAR.Enforced = true
assert.True(t, config.EnforcePushedAuthorize(ctx))
assert.Equal(t, time.Duration(0), config.PAR.ContextLifespan)
assert.Equal(t, time.Minute*5, config.GetPushedAuthorizeContextLifespan(ctx))
assert.Equal(t, time.Minute*5, config.PAR.ContextLifespan)
}

View File

@ -53,7 +53,7 @@ const (
)
const (
urnPARPrefix = "urn:ietf:params:oauth:request_uri:"
RedirectURIPrefixPushedAuthorizationRequestURN = "urn:ietf:params:oauth:request_uri:"
)
const (
@ -175,9 +175,9 @@ const (
tokenPrefixOrgAutheliaFmt = "authelia_%s_" //nolint:gosec
tokenPrefixOrgOryFmt = "ory_%s_" //nolint:gosec
tokenPrefixPartAccessToken = "at"
tokenPrefixPartRefreshToken = "rt"
tokenPrefixPartAuthorizeCode = "ac"
TokenPrefixPartAccessToken = "at"
TokenPrefixPartRefreshToken = "rt"
TokenPrefixPartAuthorizeCode = "ac"
)
// Paths.

View File

@ -1,8 +1,7 @@
package oidc
package oidc_test
import (
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"crypto/x509"
"fmt"
@ -10,20 +9,26 @@ import (
"os"
"strings"
"github.com/ory/fosite"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/utils"
)
const (
pathCrypto = "../configuration/test_resources/crypto/%s.%s"
myclient = "myclient"
myclientdesc = "My Client"
onefactor = "one_factor"
twofactor = "two_factor"
examplecom = "https://example.com"
examplecomsid = "example.com"
badsecret = "$plaintext$a_bad_secret"
badhmac = "asbdhaaskmdlkamdklasmdlkams"
pathCrypto = "../configuration/test_resources/crypto/%s.%s"
myclient = "myclient"
myclientdesc = "My Client"
onefactor = "one_factor"
twofactor = "two_factor"
examplecom = "https://example.com"
examplecomsid = "example.com"
badhmac = "asbdhaaskmdlkamdklasmdlkams"
badTokenString = "badTokenString"
)
const (
rs256 = "rs256"
)
func MustDecodeSecret(value string) *schema.PasswordDigest {
@ -78,28 +83,6 @@ func MustLoadCertificateChain(alg, op string) schema.X509CertificateChain {
}
}
func MustLoadCertificate(alg, op string) *x509.Certificate {
decoded := MustLoadCrypto(alg, op, "crt")
cert, ok := decoded.(*x509.Certificate)
if !ok {
panic(fmt.Errorf("the key was not a *x509.Certificate, it's a %T", cert))
}
return cert
}
func MustLoadEd15519PrivateKey(curve string, extra ...string) ed25519.PrivateKey {
decoded := MustLoadCrypto("ED25519", curve, "pem", extra...)
key, ok := decoded.(ed25519.PrivateKey)
if !ok {
panic(fmt.Errorf("the key was not a ed25519.PrivateKey, it's a %T", key))
}
return key
}
func MustLoadECDSAPrivateKey(curve string, extra ...string) *ecdsa.PrivateKey {
decoded := MustLoadCrypto("ECDSA", curve, "pem", extra...)
@ -133,6 +116,24 @@ func MustLoadRSAPrivateKey(bits string, extra ...string) *rsa.PrivateKey {
return key
}
type RFC6749ErrorTest struct {
*fosite.RFC6749Error
}
func (err *RFC6749ErrorTest) Error() string {
return err.WithExposeDebug(true).GetDescription()
}
func ErrorToRFC6749ErrorTest(err error) (rfc error) {
if err == nil {
return nil
}
ferr := fosite.ErrorToRFC6749Error(err)
return &RFC6749ErrorTest{ferr}
}
var (
tOpenIDConnectPBKDF2ClientSecret, tOpenIDConnectPlainTextClientSecret *schema.PasswordDigest
@ -146,8 +147,8 @@ var (
)
func init() {
tOpenIDConnectPBKDF2ClientSecret = MustDecodeSecret("$pbkdf2-sha512$310000$c8p78n7pUMln0jzvd4aK4Q$JNRBzwAo0ek5qKn50cFzzvE9RXV88h1wJn5KGiHrD0YKtZaR/nCb2CJPOsKaPK0hjf.9yHxzQGZziziccp6Yng")
tOpenIDConnectPlainTextClientSecret = MustDecodeSecret("$plaintext$example")
tOpenIDConnectPBKDF2ClientSecret = MustDecodeSecret("$pbkdf2-sha512$100000$cfNEo93VkIUIvaXHqetFoQ$O6qFLAlwCMz6.hv9XqUEPnMtrFxODw70T7bmnfTzfNPi3iXbgUEmGiyA6msybOfmj7m3QJS6lLy4DglgJifkKw")
tOpenIDConnectPlainTextClientSecret = MustDecodeSecret("$plaintext$client-secret")
keyRSA1024 = MustLoadRSAPrivateKey("1024")
keyRSA2048 = MustLoadRSAPrivateKey("2048")

View File

@ -22,22 +22,21 @@ type HMACCoreStrategy struct {
}
// AccessTokenSignature implements oauth2.AccessTokenStrategy.
func (h *HMACCoreStrategy) AccessTokenSignature(ctx context.Context, token string) string {
return h.Enigma.Signature(token)
func (h *HMACCoreStrategy) AccessTokenSignature(ctx context.Context, tokenString string) string {
return h.Enigma.Signature(tokenString)
}
// GenerateAccessToken implements oauth2.AccessTokenStrategy.
func (h *HMACCoreStrategy) GenerateAccessToken(ctx context.Context, _ fosite.Requester) (token string, signature string, err error) {
token, sig, err := h.Enigma.Generate(ctx)
if err != nil {
func (h *HMACCoreStrategy) GenerateAccessToken(ctx context.Context, _ fosite.Requester) (tokenString string, sig string, err error) {
if tokenString, sig, err = h.Enigma.Generate(ctx); err != nil {
return "", "", err
}
return h.setPrefix(token, tokenPrefixPartAccessToken), sig, nil
return h.setPrefix(tokenString, TokenPrefixPartAccessToken), sig, nil
}
// ValidateAccessToken implements oauth2.AccessTokenStrategy.
func (h *HMACCoreStrategy) ValidateAccessToken(ctx context.Context, r fosite.Requester, token string) (err error) {
func (h *HMACCoreStrategy) ValidateAccessToken(ctx context.Context, r fosite.Requester, tokenString string) (err error) {
var exp = r.GetSession().GetExpiresAt(fosite.AccessToken)
if exp.IsZero() && r.GetRequestedAt().Add(h.Config.GetAccessTokenLifespan(ctx)).Before(time.Now().UTC()) {
return errorsx.WithStack(fosite.ErrTokenExpired.WithHintf("Access token expired at '%s'.", r.GetRequestedAt().Add(h.Config.GetAccessTokenLifespan(ctx))))
@ -47,37 +46,36 @@ func (h *HMACCoreStrategy) ValidateAccessToken(ctx context.Context, r fosite.Req
return errorsx.WithStack(fosite.ErrTokenExpired.WithHintf("Access token expired at '%s'.", exp))
}
return h.Enigma.Validate(ctx, h.trimPrefix(token, tokenPrefixPartAccessToken))
return h.Enigma.Validate(ctx, h.trimPrefix(tokenString, TokenPrefixPartAccessToken))
}
// RefreshTokenSignature implements oauth2.RefreshTokenStrategy.
func (h *HMACCoreStrategy) RefreshTokenSignature(ctx context.Context, token string) string {
return h.Enigma.Signature(token)
func (h *HMACCoreStrategy) RefreshTokenSignature(ctx context.Context, tokenString string) string {
return h.Enigma.Signature(tokenString)
}
// GenerateRefreshToken implements oauth2.RefreshTokenStrategy.
func (h *HMACCoreStrategy) GenerateRefreshToken(ctx context.Context, _ fosite.Requester) (token string, signature string, err error) {
token, sig, err := h.Enigma.Generate(ctx)
if err != nil {
func (h *HMACCoreStrategy) GenerateRefreshToken(ctx context.Context, _ fosite.Requester) (tokenString string, sig string, err error) {
if tokenString, sig, err = h.Enigma.Generate(ctx); err != nil {
return "", "", err
}
return h.setPrefix(token, tokenPrefixPartRefreshToken), sig, nil
return h.setPrefix(tokenString, TokenPrefixPartRefreshToken), sig, nil
}
// ValidateRefreshToken implements oauth2.RefreshTokenStrategy.
func (h *HMACCoreStrategy) ValidateRefreshToken(ctx context.Context, r fosite.Requester, token string) (err error) {
func (h *HMACCoreStrategy) ValidateRefreshToken(ctx context.Context, r fosite.Requester, tokenString string) (err error) {
var exp = r.GetSession().GetExpiresAt(fosite.RefreshToken)
if exp.IsZero() {
return h.Enigma.Validate(ctx, h.trimPrefix(token, tokenPrefixPartRefreshToken))
return h.Enigma.Validate(ctx, h.trimPrefix(tokenString, TokenPrefixPartRefreshToken))
}
if exp.Before(time.Now().UTC()) {
return errorsx.WithStack(fosite.ErrTokenExpired.WithHintf("Refresh token expired at '%s'.", exp))
}
return h.Enigma.Validate(ctx, h.trimPrefix(token, tokenPrefixPartRefreshToken))
return h.Enigma.Validate(ctx, h.trimPrefix(tokenString, TokenPrefixPartRefreshToken))
}
// AuthorizeCodeSignature implements oauth2.AuthorizeCodeStrategy.
@ -86,17 +84,16 @@ func (h *HMACCoreStrategy) AuthorizeCodeSignature(ctx context.Context, token str
}
// GenerateAuthorizeCode implements oauth2.AuthorizeCodeStrategy.
func (h *HMACCoreStrategy) GenerateAuthorizeCode(ctx context.Context, _ fosite.Requester) (token string, signature string, err error) {
token, sig, err := h.Enigma.Generate(ctx)
if err != nil {
func (h *HMACCoreStrategy) GenerateAuthorizeCode(ctx context.Context, _ fosite.Requester) (tokenString string, sig string, err error) {
if tokenString, sig, err = h.Enigma.Generate(ctx); err != nil {
return "", "", err
}
return h.setPrefix(token, tokenPrefixPartAuthorizeCode), sig, nil
return h.setPrefix(tokenString, TokenPrefixPartAuthorizeCode), sig, nil
}
// ValidateAuthorizeCode implements oauth2.AuthorizeCodeStrategy.
func (h *HMACCoreStrategy) ValidateAuthorizeCode(ctx context.Context, r fosite.Requester, token string) (err error) {
func (h *HMACCoreStrategy) ValidateAuthorizeCode(ctx context.Context, r fosite.Requester, tokenString string) (err error) {
var exp = r.GetSession().GetExpiresAt(fosite.AuthorizeCode)
if exp.IsZero() && r.GetRequestedAt().Add(h.Config.GetAuthorizeCodeLifespan(ctx)).Before(time.Now().UTC()) {
@ -107,7 +104,7 @@ func (h *HMACCoreStrategy) ValidateAuthorizeCode(ctx context.Context, r fosite.R
return errorsx.WithStack(fosite.ErrTokenExpired.WithHintf("Authorize code expired at '%s'.", exp))
}
return h.Enigma.Validate(ctx, h.trimPrefix(token, tokenPrefixPartAuthorizeCode))
return h.Enigma.Validate(ctx, h.trimPrefix(tokenString, TokenPrefixPartAuthorizeCode))
}
func (h *HMACCoreStrategy) getPrefix(part string) string {
@ -118,14 +115,14 @@ func (h *HMACCoreStrategy) getCustomPrefix(tokenPrefixFmt, part string) string {
return fmt.Sprintf(tokenPrefixFmt, part)
}
func (h *HMACCoreStrategy) setPrefix(token, part string) string {
return h.getPrefix(part) + token
func (h *HMACCoreStrategy) setPrefix(tokenString, part string) string {
return h.getPrefix(part) + tokenString
}
func (h *HMACCoreStrategy) trimPrefix(token, part string) string {
if strings.HasPrefix(token, h.getCustomPrefix(tokenPrefixOrgOryFmt, part)) {
return strings.TrimPrefix(token, h.getCustomPrefix(tokenPrefixOrgOryFmt, part))
func (h *HMACCoreStrategy) trimPrefix(tokenString, part string) string {
if strings.HasPrefix(tokenString, h.getCustomPrefix(tokenPrefixOrgOryFmt, part)) {
return strings.TrimPrefix(tokenString, h.getCustomPrefix(tokenPrefixOrgOryFmt, part))
}
return strings.TrimPrefix(token, h.getPrefix(part))
return strings.TrimPrefix(tokenString, h.getPrefix(part))
}

View File

@ -1,33 +1,33 @@
package oidc
package oidc_test
import (
"context"
"fmt"
"regexp"
"strings"
"testing"
"time"
"github.com/ory/fosite"
"github.com/ory/fosite/token/hmac"
"github.com/stretchr/testify/assert"
"github.com/authelia/authelia/v4/internal/oidc"
)
func TestHMACStrategy(t *testing.T) {
goodsecret := []byte("R7VCSUfnKc7Y5zE84q6GstYqfMGjL4wM")
secreta := []byte("a")
config := &Config{
config := &oidc.Config{
TokenEntropy: 10,
GlobalSecret: secreta,
Lifespans: LifespanConfig{
Lifespans: oidc.LifespanConfig{
AccessToken: time.Hour,
RefreshToken: time.Hour,
AuthorizeCode: time.Minute,
},
}
strategy := &HMACCoreStrategy{
strategy := &oidc.HMACCoreStrategy{
Enigma: &hmac.HMACStrategy{Config: config},
Config: config,
}
@ -87,50 +87,3 @@ func TestHMACStrategy(t *testing.T) {
assert.NoError(t, strategy.ValidateAccessToken(ctx, &fosite.Request{RequestedAt: time.Now().Add(time.Hour * -2400), Session: &fosite.DefaultSession{ExpiresAt: map[fosite.TokenType]time.Time{fosite.AccessToken: time.Now().Add(100 * time.Hour)}}}, token))
assert.EqualError(t, strategy.ValidateAccessToken(ctx, &fosite.Request{RequestedAt: time.Now(), Session: &fosite.DefaultSession{ExpiresAt: map[fosite.TokenType]time.Time{fosite.AccessToken: time.Now().Add(-100 * time.Second)}}}, token), "invalid_token")
}
func TestHMACCoreStrategy_TrimPrefix(t *testing.T) {
testCases := []struct {
name string
have string
part string
expected string
}{
{"ShouldTrimAutheliaPrefix", "authelia_at_example", tokenPrefixPartAccessToken, "example"},
{"ShouldTrimOryPrefix", "ory_at_example", tokenPrefixPartAccessToken, "example"},
{"ShouldTrimOnlyAutheliaPrefix", "authelia_at_ory_at_example", tokenPrefixPartAccessToken, "ory_at_example"},
{"ShouldTrimOnlyOryPrefix", "ory_at_authelia_at_example", tokenPrefixPartAccessToken, "authelia_at_example"},
{"ShouldNotTrimGitHubPrefix", "gh_at_example", tokenPrefixPartAccessToken, "gh_at_example"},
}
strategy := &HMACCoreStrategy{}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expected, strategy.trimPrefix(tc.have, tc.part))
})
}
}
func TestHMACCoreStrategy_GetSetPrefix(t *testing.T) {
testCases := []struct {
name string
have string
expectedSet string
expectedGet string
}{
{"ShouldAddPrefix", "example", "authelia_%s_example", "authelia_%s_"},
}
strategy := &HMACCoreStrategy{}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
for _, part := range []string{tokenPrefixPartAccessToken, tokenPrefixPartAuthorizeCode, tokenPrefixPartRefreshToken} {
t.Run(strings.ToUpper(part), func(t *testing.T) {
assert.Equal(t, fmt.Sprintf(tc.expectedSet, part), strategy.setPrefix(tc.have, part))
assert.Equal(t, fmt.Sprintf(tc.expectedGet, part), strategy.getPrefix(part))
})
}
})
}
}

View File

@ -0,0 +1,56 @@
package oidc
import (
"fmt"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestHMACCoreStrategy_TrimPrefix(t *testing.T) {
testCases := []struct {
name string
have string
part string
expected string
}{
{"ShouldTrimAutheliaPrefix", "authelia_at_example", TokenPrefixPartAccessToken, "example"},
{"ShouldTrimOryPrefix", "ory_at_example", TokenPrefixPartAccessToken, "example"},
{"ShouldTrimOnlyAutheliaPrefix", "authelia_at_ory_at_example", TokenPrefixPartAccessToken, "ory_at_example"},
{"ShouldTrimOnlyOryPrefix", "ory_at_authelia_at_example", TokenPrefixPartAccessToken, "authelia_at_example"},
{"ShouldNotTrimGitHubPrefix", "gh_at_example", TokenPrefixPartAccessToken, "gh_at_example"},
}
strategy := &HMACCoreStrategy{}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expected, strategy.trimPrefix(tc.have, tc.part))
})
}
}
func TestHMACCoreStrategy_GetSetPrefix(t *testing.T) {
testCases := []struct {
name string
have string
expectedSet string
expectedGet string
}{
{"ShouldAddPrefix", "example", "authelia_%s_example", "authelia_%s_"},
}
strategy := &HMACCoreStrategy{}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
for _, part := range []string{TokenPrefixPartAccessToken, TokenPrefixPartAuthorizeCode, TokenPrefixPartRefreshToken} {
t.Run(strings.ToUpper(part), func(t *testing.T) {
assert.Equal(t, fmt.Sprintf(tc.expectedSet, part), strategy.setPrefix(tc.have, part))
assert.Equal(t, fmt.Sprintf(tc.expectedGet, part), strategy.getPrefix(part))
})
}
})
}
}

View File

@ -66,6 +66,7 @@ func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration
ClientAuthMethodClientSecretBasic,
ClientAuthMethodClientSecretPost,
ClientAuthMethodClientSecretJWT,
ClientAuthMethodPrivateKeyJWT,
ClientAuthMethodNone,
},
TokenEndpointAuthSigningAlgValuesSupported: []string{
@ -82,6 +83,7 @@ func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration
ClientAuthMethodClientSecretBasic,
ClientAuthMethodClientSecretPost,
ClientAuthMethodClientSecretJWT,
ClientAuthMethodPrivateKeyJWT,
ClientAuthMethodNone,
},
RevocationEndpointAuthSigningAlgValuesSupported: []string{
@ -104,12 +106,12 @@ func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration
SigningAlgRSAUsingSHA256,
},
UserinfoSigningAlgValuesSupported: []string{
SigningAlgNone,
SigningAlgRSAUsingSHA256,
SigningAlgNone,
},
RequestObjectSigningAlgValuesSupported: []string{
SigningAlgNone,
SigningAlgRSAUsingSHA256,
SigningAlgNone,
},
},
OpenIDConnectFrontChannelLogoutDiscoveryOptions: &OpenIDConnectFrontChannelLogoutDiscoveryOptions{},
@ -122,11 +124,7 @@ func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration
},
}
algs := make([]string, len(c.Discovery.RegisteredJWKSigningAlgs))
copy(algs, c.Discovery.RegisteredJWKSigningAlgs)
for _, alg := range algs {
for _, alg := range c.Discovery.ResponseObjectSigningAlgs {
if !utils.IsStringInSlice(alg, config.IDTokenSigningAlgValuesSupported) {
config.IDTokenSigningAlgValuesSupported = append(config.IDTokenSigningAlgValuesSupported, alg)
}
@ -136,6 +134,20 @@ func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration
}
}
for _, alg := range c.Discovery.RequestObjectSigningAlgs {
if !utils.IsStringInSlice(alg, config.RequestObjectSigningAlgValuesSupported) {
config.RequestObjectSigningAlgValuesSupported = append(config.RequestObjectSigningAlgValuesSupported, alg)
}
if !utils.IsStringInSlice(alg, config.RevocationEndpointAuthSigningAlgValuesSupported) {
config.RevocationEndpointAuthSigningAlgValuesSupported = append(config.RevocationEndpointAuthSigningAlgValuesSupported, alg)
}
if !utils.IsStringInSlice(alg, config.TokenEndpointAuthSigningAlgValuesSupported) {
config.TokenEndpointAuthSigningAlgValuesSupported = append(config.TokenEndpointAuthSigningAlgValuesSupported, alg)
}
}
sort.Sort(SortedSigningAlgs(config.IDTokenSigningAlgValuesSupported))
sort.Sort(SortedSigningAlgs(config.UserinfoSigningAlgValuesSupported))
sort.Sort(SortedSigningAlgs(config.RequestObjectSigningAlgValuesSupported))

View File

@ -1,11 +1,14 @@
package oidc
package oidc_test
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/oidc"
)
func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
@ -13,91 +16,119 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
desc string
pkcePlainChallenge bool
enforcePAR bool
clients map[string]Client
clients map[string]oidc.Client
discovery schema.OpenIDConnectDiscovery
expectCodeChallengeMethodsSupported, expectSubjectTypesSupported, expectedIDTokenSigAlgsSupported, expectedUserInfoSigAlgsSupported []string
expectCodeChallengeMethodsSupported, expectSubjectTypesSupported []string
expectedIDTokenSigAlgsSupported, expectedUserInfoSigAlgsSupported []string
expectedRequestObjectSigAlgsSupported, expectedRevocationSigAlgsSupported, expectedTokenAuthSigAlgsSupported []string
}{
{
desc: "ShouldHaveChallengeMethodsS256ANDSubjectTypesSupportedPublic",
pkcePlainChallenge: false,
clients: map[string]Client{"a": &BaseClient{}},
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256},
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
expectedIDTokenSigAlgsSupported: []string{SigningAlgRSAUsingSHA256},
expectedUserInfoSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgNone},
desc: "ShouldHaveChallengeMethodsS256ANDSubjectTypesSupportedPublic",
pkcePlainChallenge: false,
clients: map[string]oidc.Client{"a": &oidc.BaseClient{}},
expectCodeChallengeMethodsSupported: []string{oidc.PKCEChallengeMethodSHA256},
expectSubjectTypesSupported: []string{oidc.SubjectTypePublic, oidc.SubjectTypePairwise},
expectedIDTokenSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256},
expectedUserInfoSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone},
expectedRequestObjectSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone},
expectedRevocationSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512},
expectedTokenAuthSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512},
},
{
desc: "ShouldIncludDiscoveryInfo",
desc: "ShouldIncludeDiscoveryInfo",
pkcePlainChallenge: false,
clients: map[string]Client{"a": &BaseClient{}},
clients: map[string]oidc.Client{"a": &oidc.BaseClient{}},
discovery: schema.OpenIDConnectDiscovery{
RegisteredJWKSigningAlgs: []string{SigningAlgECDSAUsingP521AndSHA512},
ResponseObjectSigningAlgs: []string{oidc.SigningAlgECDSAUsingP521AndSHA512},
RequestObjectSigningAlgs: []string{oidc.SigningAlgECDSAUsingP256AndSHA256},
},
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256},
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
expectedIDTokenSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgECDSAUsingP521AndSHA512},
expectedUserInfoSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgECDSAUsingP521AndSHA512, SigningAlgNone},
expectCodeChallengeMethodsSupported: []string{oidc.PKCEChallengeMethodSHA256},
expectSubjectTypesSupported: []string{oidc.SubjectTypePublic, oidc.SubjectTypePairwise},
expectedIDTokenSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgECDSAUsingP521AndSHA512},
expectedUserInfoSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgECDSAUsingP521AndSHA512, oidc.SigningAlgNone},
expectedRequestObjectSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgECDSAUsingP256AndSHA256, oidc.SigningAlgNone},
expectedRevocationSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512, oidc.SigningAlgECDSAUsingP256AndSHA256},
expectedTokenAuthSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512, oidc.SigningAlgECDSAUsingP256AndSHA256},
},
{
desc: "ShouldHaveChallengeMethodsS256PlainANDSubjectTypesSupportedPublic",
pkcePlainChallenge: true,
clients: map[string]Client{"a": &BaseClient{}},
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
expectedIDTokenSigAlgsSupported: []string{SigningAlgRSAUsingSHA256},
expectedUserInfoSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgNone},
desc: "ShouldHaveChallengeMethodsS256PlainANDSubjectTypesSupportedPublic",
pkcePlainChallenge: true,
clients: map[string]oidc.Client{"a": &oidc.BaseClient{}},
expectCodeChallengeMethodsSupported: []string{oidc.PKCEChallengeMethodSHA256, oidc.PKCEChallengeMethodPlain},
expectSubjectTypesSupported: []string{oidc.SubjectTypePublic, oidc.SubjectTypePairwise},
expectedIDTokenSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256},
expectedUserInfoSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone},
expectedRequestObjectSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone},
expectedRevocationSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512},
expectedTokenAuthSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512},
},
{
desc: "ShouldHaveChallengeMethodsS256ANDSubjectTypesSupportedPublicPairwise",
pkcePlainChallenge: false,
clients: map[string]Client{"a": &BaseClient{SectorIdentifier: "yes"}},
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256},
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
expectedIDTokenSigAlgsSupported: []string{SigningAlgRSAUsingSHA256},
expectedUserInfoSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgNone},
desc: "ShouldHaveChallengeMethodsS256ANDSubjectTypesSupportedPublicPairwise",
pkcePlainChallenge: false,
clients: map[string]oidc.Client{"a": &oidc.BaseClient{SectorIdentifier: "yes"}},
expectCodeChallengeMethodsSupported: []string{oidc.PKCEChallengeMethodSHA256},
expectSubjectTypesSupported: []string{oidc.SubjectTypePublic, oidc.SubjectTypePairwise},
expectedIDTokenSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256},
expectedUserInfoSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone},
expectedRequestObjectSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone},
expectedRevocationSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512},
expectedTokenAuthSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512},
},
{
desc: "ShouldHaveChallengeMethodsS256PlainANDSubjectTypesSupportedPublicPairwise",
pkcePlainChallenge: true,
clients: map[string]Client{"a": &BaseClient{SectorIdentifier: "yes"}},
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
expectedIDTokenSigAlgsSupported: []string{SigningAlgRSAUsingSHA256},
expectedUserInfoSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgNone},
desc: "ShouldHaveChallengeMethodsS256PlainANDSubjectTypesSupportedPublicPairwise",
pkcePlainChallenge: true,
clients: map[string]oidc.Client{"a": &oidc.BaseClient{SectorIdentifier: "yes"}},
expectCodeChallengeMethodsSupported: []string{oidc.PKCEChallengeMethodSHA256, oidc.PKCEChallengeMethodPlain},
expectSubjectTypesSupported: []string{oidc.SubjectTypePublic, oidc.SubjectTypePairwise},
expectedIDTokenSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256},
expectedUserInfoSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone},
expectedRequestObjectSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone},
expectedRevocationSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512},
expectedTokenAuthSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512},
},
{
desc: "ShouldHaveTokenAuthMethodsNone",
pkcePlainChallenge: true,
clients: map[string]Client{"a": &BaseClient{SectorIdentifier: "yes"}},
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
expectedIDTokenSigAlgsSupported: []string{SigningAlgRSAUsingSHA256},
expectedUserInfoSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgNone},
desc: "ShouldHaveTokenAuthMethodsNone",
pkcePlainChallenge: true,
clients: map[string]oidc.Client{"a": &oidc.BaseClient{SectorIdentifier: "yes"}},
expectCodeChallengeMethodsSupported: []string{oidc.PKCEChallengeMethodSHA256, oidc.PKCEChallengeMethodPlain},
expectSubjectTypesSupported: []string{oidc.SubjectTypePublic, oidc.SubjectTypePairwise},
expectedIDTokenSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256},
expectedUserInfoSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone},
expectedRequestObjectSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone},
expectedRevocationSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512},
expectedTokenAuthSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512},
},
{
desc: "ShouldHaveTokenAuthMethodsNone",
pkcePlainChallenge: true,
clients: map[string]Client{
"a": &BaseClient{SectorIdentifier: "yes"},
"b": &BaseClient{SectorIdentifier: "yes"},
clients: map[string]oidc.Client{
"a": &oidc.BaseClient{SectorIdentifier: "yes"},
"b": &oidc.BaseClient{SectorIdentifier: "yes"},
},
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
expectedIDTokenSigAlgsSupported: []string{SigningAlgRSAUsingSHA256},
expectedUserInfoSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgNone},
expectCodeChallengeMethodsSupported: []string{oidc.PKCEChallengeMethodSHA256, oidc.PKCEChallengeMethodPlain},
expectSubjectTypesSupported: []string{oidc.SubjectTypePublic, oidc.SubjectTypePairwise},
expectedIDTokenSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256},
expectedUserInfoSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone},
expectedRequestObjectSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone},
expectedRevocationSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512},
expectedTokenAuthSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512},
},
{
desc: "ShouldHaveTokenAuthMethodsNone",
pkcePlainChallenge: true,
clients: map[string]Client{
"a": &BaseClient{SectorIdentifier: "yes"},
"b": &BaseClient{SectorIdentifier: "yes"},
clients: map[string]oidc.Client{
"a": &oidc.BaseClient{SectorIdentifier: "yes"},
"b": &oidc.BaseClient{SectorIdentifier: "yes"},
},
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
expectedIDTokenSigAlgsSupported: []string{SigningAlgRSAUsingSHA256},
expectedUserInfoSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgNone},
expectCodeChallengeMethodsSupported: []string{oidc.PKCEChallengeMethodSHA256, oidc.PKCEChallengeMethodPlain},
expectSubjectTypesSupported: []string{oidc.SubjectTypePublic, oidc.SubjectTypePairwise},
expectedIDTokenSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256},
expectedUserInfoSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone},
expectedRequestObjectSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone},
expectedRevocationSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512},
expectedTokenAuthSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512},
},
}
@ -111,7 +142,7 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
Discovery: tc.discovery,
}
actual := NewOpenIDConnectWellKnownConfiguration(&c)
actual := oidc.NewOpenIDConnectWellKnownConfiguration(&c)
for _, codeChallengeMethod := range tc.expectCodeChallengeMethodsSupported {
assert.Contains(t, actual.CodeChallengeMethodsSupported, codeChallengeMethod)
}
@ -130,6 +161,425 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
assert.Equal(t, tc.expectedUserInfoSigAlgsSupported, actual.UserinfoSigningAlgValuesSupported)
assert.Equal(t, tc.expectedIDTokenSigAlgsSupported, actual.IDTokenSigningAlgValuesSupported)
assert.Equal(t, tc.expectedRequestObjectSigAlgsSupported, actual.RequestObjectSigningAlgValuesSupported)
assert.Equal(t, tc.expectedRevocationSigAlgsSupported, actual.RevocationEndpointAuthSigningAlgValuesSupported)
assert.Equal(t, tc.expectedTokenAuthSigAlgsSupported, actual.TokenEndpointAuthSigningAlgValuesSupported)
})
}
}
func TestNewOpenIDConnectProviderDiscovery(t *testing.T) {
provider := oidc.NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: keyRSA2048,
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
EnablePKCEPlainChallenge: true,
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "a-client",
Secret: tOpenIDConnectPlainTextClientSecret,
Policy: onefactor,
RedirectURIs: []string{
"https://google.com",
},
},
},
}, nil, nil)
a := provider.GetOpenIDConnectWellKnownConfiguration("https://auth.example.com")
data, err := json.Marshal(&a)
assert.NoError(t, err)
b := oidc.OpenIDConnectWellKnownConfiguration{}
assert.NoError(t, json.Unmarshal(data, &b))
assert.Equal(t, a, b)
y := provider.GetOAuth2WellKnownConfiguration("https://auth.example.com")
data, err = json.Marshal(&y)
assert.NoError(t, err)
z := oidc.OAuth2WellKnownConfiguration{}
assert.NoError(t, json.Unmarshal(data, &z))
assert.Equal(t, y, z)
}
func TestNewOpenIDConnectProvider_GetOpenIDConnectWellKnownConfiguration(t *testing.T) {
provider := oidc.NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: keyRSA2048,
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "a-client",
Secret: tOpenIDConnectPlainTextClientSecret,
Policy: onefactor,
RedirectURIs: []string{
"https://google.com",
},
},
},
}, nil, nil)
require.NotNil(t, provider)
disco := provider.GetOpenIDConnectWellKnownConfiguration(examplecom)
assert.Equal(t, examplecom, disco.Issuer)
assert.Equal(t, "https://example.com/jwks.json", disco.JWKSURI)
assert.Equal(t, "https://example.com/api/oidc/authorization", disco.AuthorizationEndpoint)
assert.Equal(t, "https://example.com/api/oidc/token", disco.TokenEndpoint)
assert.Equal(t, "https://example.com/api/oidc/userinfo", disco.UserinfoEndpoint)
assert.Equal(t, "https://example.com/api/oidc/introspection", disco.IntrospectionEndpoint)
assert.Equal(t, "https://example.com/api/oidc/revocation", disco.RevocationEndpoint)
assert.Equal(t, "", disco.RegistrationEndpoint)
assert.Len(t, disco.CodeChallengeMethodsSupported, 1)
assert.Contains(t, disco.CodeChallengeMethodsSupported, oidc.PKCEChallengeMethodSHA256)
assert.Len(t, disco.ScopesSupported, 5)
assert.Contains(t, disco.ScopesSupported, oidc.ScopeOpenID)
assert.Contains(t, disco.ScopesSupported, oidc.ScopeOfflineAccess)
assert.Contains(t, disco.ScopesSupported, oidc.ScopeProfile)
assert.Contains(t, disco.ScopesSupported, oidc.ScopeGroups)
assert.Contains(t, disco.ScopesSupported, oidc.ScopeEmail)
assert.Len(t, disco.ResponseModesSupported, 3)
assert.Contains(t, disco.ResponseModesSupported, oidc.ResponseModeFormPost)
assert.Contains(t, disco.ResponseModesSupported, oidc.ResponseModeQuery)
assert.Contains(t, disco.ResponseModesSupported, oidc.ResponseModeFragment)
assert.Len(t, disco.SubjectTypesSupported, 2)
assert.Contains(t, disco.SubjectTypesSupported, oidc.SubjectTypePublic)
assert.Contains(t, disco.SubjectTypesSupported, oidc.SubjectTypePairwise)
assert.Len(t, disco.ResponseTypesSupported, 7)
assert.Contains(t, disco.ResponseTypesSupported, oidc.ResponseTypeAuthorizationCodeFlow)
assert.Contains(t, disco.ResponseTypesSupported, oidc.ResponseTypeImplicitFlowIDToken)
assert.Contains(t, disco.ResponseTypesSupported, oidc.ResponseTypeImplicitFlowToken)
assert.Contains(t, disco.ResponseTypesSupported, oidc.ResponseTypeImplicitFlowBoth)
assert.Contains(t, disco.ResponseTypesSupported, oidc.ResponseTypeHybridFlowIDToken)
assert.Contains(t, disco.ResponseTypesSupported, oidc.ResponseTypeHybridFlowToken)
assert.Contains(t, disco.ResponseTypesSupported, oidc.ResponseTypeHybridFlowBoth)
assert.Len(t, disco.TokenEndpointAuthMethodsSupported, 5)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, oidc.ClientAuthMethodClientSecretBasic)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, oidc.ClientAuthMethodClientSecretPost)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, oidc.ClientAuthMethodClientSecretJWT)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, oidc.ClientAuthMethodPrivateKeyJWT)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, oidc.ClientAuthMethodNone)
assert.Len(t, disco.RevocationEndpointAuthMethodsSupported, 5)
assert.Contains(t, disco.RevocationEndpointAuthMethodsSupported, oidc.ClientAuthMethodClientSecretBasic)
assert.Contains(t, disco.RevocationEndpointAuthMethodsSupported, oidc.ClientAuthMethodClientSecretPost)
assert.Contains(t, disco.RevocationEndpointAuthMethodsSupported, oidc.ClientAuthMethodClientSecretJWT)
assert.Contains(t, disco.RevocationEndpointAuthMethodsSupported, oidc.ClientAuthMethodPrivateKeyJWT)
assert.Contains(t, disco.RevocationEndpointAuthMethodsSupported, oidc.ClientAuthMethodNone)
assert.Len(t, disco.IntrospectionEndpointAuthMethodsSupported, 2)
assert.Contains(t, disco.IntrospectionEndpointAuthMethodsSupported, oidc.ClientAuthMethodClientSecretBasic)
assert.Contains(t, disco.IntrospectionEndpointAuthMethodsSupported, oidc.ClientAuthMethodNone)
assert.Len(t, disco.GrantTypesSupported, 3)
assert.Contains(t, disco.GrantTypesSupported, oidc.GrantTypeAuthorizationCode)
assert.Contains(t, disco.GrantTypesSupported, oidc.GrantTypeRefreshToken)
assert.Contains(t, disco.GrantTypesSupported, oidc.GrantTypeImplicit)
assert.Len(t, disco.RevocationEndpointAuthSigningAlgValuesSupported, 3)
assert.Equal(t, disco.RevocationEndpointAuthSigningAlgValuesSupported[0], oidc.SigningAlgHMACUsingSHA256)
assert.Equal(t, disco.RevocationEndpointAuthSigningAlgValuesSupported[1], oidc.SigningAlgHMACUsingSHA384)
assert.Equal(t, disco.RevocationEndpointAuthSigningAlgValuesSupported[2], oidc.SigningAlgHMACUsingSHA512)
assert.Len(t, disco.TokenEndpointAuthSigningAlgValuesSupported, 3)
assert.Equal(t, disco.TokenEndpointAuthSigningAlgValuesSupported[0], oidc.SigningAlgHMACUsingSHA256)
assert.Equal(t, disco.TokenEndpointAuthSigningAlgValuesSupported[1], oidc.SigningAlgHMACUsingSHA384)
assert.Equal(t, disco.TokenEndpointAuthSigningAlgValuesSupported[2], oidc.SigningAlgHMACUsingSHA512)
assert.Len(t, disco.IDTokenSigningAlgValuesSupported, 1)
assert.Contains(t, disco.IDTokenSigningAlgValuesSupported, oidc.SigningAlgRSAUsingSHA256)
assert.Len(t, disco.UserinfoSigningAlgValuesSupported, 2)
assert.Equal(t, disco.UserinfoSigningAlgValuesSupported[0], oidc.SigningAlgRSAUsingSHA256)
assert.Equal(t, disco.UserinfoSigningAlgValuesSupported[1], oidc.SigningAlgNone)
require.Len(t, disco.RequestObjectSigningAlgValuesSupported, 2)
assert.Equal(t, oidc.SigningAlgRSAUsingSHA256, disco.RequestObjectSigningAlgValuesSupported[0])
assert.Equal(t, oidc.SigningAlgNone, disco.RequestObjectSigningAlgValuesSupported[1])
assert.Len(t, disco.ClaimsSupported, 18)
assert.Contains(t, disco.ClaimsSupported, oidc.ClaimAuthenticationMethodsReference)
assert.Contains(t, disco.ClaimsSupported, oidc.ClaimAudience)
assert.Contains(t, disco.ClaimsSupported, oidc.ClaimAuthorizedParty)
assert.Contains(t, disco.ClaimsSupported, oidc.ClaimClientIdentifier)
assert.Contains(t, disco.ClaimsSupported, oidc.ClaimExpirationTime)
assert.Contains(t, disco.ClaimsSupported, oidc.ClaimIssuedAt)
assert.Contains(t, disco.ClaimsSupported, oidc.ClaimIssuer)
assert.Contains(t, disco.ClaimsSupported, oidc.ClaimJWTID)
assert.Contains(t, disco.ClaimsSupported, oidc.ClaimRequestedAt)
assert.Contains(t, disco.ClaimsSupported, oidc.ClaimSubject)
assert.Contains(t, disco.ClaimsSupported, oidc.ClaimAuthenticationTime)
assert.Contains(t, disco.ClaimsSupported, oidc.ClaimNonce)
assert.Contains(t, disco.ClaimsSupported, oidc.ClaimPreferredEmail)
assert.Contains(t, disco.ClaimsSupported, oidc.ClaimEmailVerified)
assert.Contains(t, disco.ClaimsSupported, oidc.ClaimEmailAlts)
assert.Contains(t, disco.ClaimsSupported, oidc.ClaimGroups)
assert.Contains(t, disco.ClaimsSupported, oidc.ClaimPreferredUsername)
assert.Contains(t, disco.ClaimsSupported, oidc.ClaimFullName)
assert.Len(t, disco.PromptValuesSupported, 2)
assert.Contains(t, disco.PromptValuesSupported, oidc.PromptConsent)
assert.Contains(t, disco.PromptValuesSupported, oidc.PromptNone)
}
func TestNewOpenIDConnectProvider_GetOAuth2WellKnownConfiguration(t *testing.T) {
provider := oidc.NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: keyRSA2048,
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "a-client",
Secret: tOpenIDConnectPlainTextClientSecret,
Policy: onefactor,
RedirectURIs: []string{
"https://google.com",
},
},
},
}, nil, nil)
require.NotNil(t, provider)
disco := provider.GetOAuth2WellKnownConfiguration(examplecom)
assert.Equal(t, examplecom, disco.Issuer)
assert.Equal(t, "https://example.com/jwks.json", disco.JWKSURI)
assert.Equal(t, "https://example.com/api/oidc/authorization", disco.AuthorizationEndpoint)
assert.Equal(t, "https://example.com/api/oidc/token", disco.TokenEndpoint)
assert.Equal(t, "https://example.com/api/oidc/introspection", disco.IntrospectionEndpoint)
assert.Equal(t, "https://example.com/api/oidc/revocation", disco.RevocationEndpoint)
assert.Equal(t, "", disco.RegistrationEndpoint)
require.Len(t, disco.CodeChallengeMethodsSupported, 1)
assert.Equal(t, "S256", disco.CodeChallengeMethodsSupported[0])
assert.Len(t, disco.ScopesSupported, 5)
assert.Contains(t, disco.ScopesSupported, oidc.ScopeOpenID)
assert.Contains(t, disco.ScopesSupported, oidc.ScopeOfflineAccess)
assert.Contains(t, disco.ScopesSupported, oidc.ScopeProfile)
assert.Contains(t, disco.ScopesSupported, oidc.ScopeGroups)
assert.Contains(t, disco.ScopesSupported, oidc.ScopeEmail)
assert.Len(t, disco.ResponseModesSupported, 3)
assert.Contains(t, disco.ResponseModesSupported, oidc.ResponseModeFormPost)
assert.Contains(t, disco.ResponseModesSupported, oidc.ResponseModeQuery)
assert.Contains(t, disco.ResponseModesSupported, oidc.ResponseModeFragment)
assert.Len(t, disco.SubjectTypesSupported, 2)
assert.Contains(t, disco.SubjectTypesSupported, oidc.SubjectTypePublic)
assert.Contains(t, disco.SubjectTypesSupported, oidc.SubjectTypePairwise)
assert.Len(t, disco.ResponseTypesSupported, 7)
assert.Contains(t, disco.ResponseTypesSupported, oidc.ResponseTypeAuthorizationCodeFlow)
assert.Contains(t, disco.ResponseTypesSupported, oidc.ResponseTypeImplicitFlowIDToken)
assert.Contains(t, disco.ResponseTypesSupported, oidc.ResponseTypeImplicitFlowToken)
assert.Contains(t, disco.ResponseTypesSupported, oidc.ResponseTypeImplicitFlowBoth)
assert.Contains(t, disco.ResponseTypesSupported, oidc.ResponseTypeHybridFlowIDToken)
assert.Contains(t, disco.ResponseTypesSupported, oidc.ResponseTypeHybridFlowToken)
assert.Contains(t, disco.ResponseTypesSupported, oidc.ResponseTypeHybridFlowBoth)
assert.Len(t, disco.TokenEndpointAuthMethodsSupported, 5)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, oidc.ClientAuthMethodClientSecretBasic)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, oidc.ClientAuthMethodClientSecretPost)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, oidc.ClientAuthMethodClientSecretJWT)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, oidc.ClientAuthMethodPrivateKeyJWT)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, oidc.ClientAuthMethodNone)
assert.Len(t, disco.GrantTypesSupported, 3)
assert.Contains(t, disco.GrantTypesSupported, oidc.GrantTypeAuthorizationCode)
assert.Contains(t, disco.GrantTypesSupported, oidc.GrantTypeRefreshToken)
assert.Contains(t, disco.GrantTypesSupported, oidc.GrantTypeImplicit)
assert.Len(t, disco.ClaimsSupported, 18)
assert.Contains(t, disco.ClaimsSupported, oidc.ClaimAuthenticationMethodsReference)
assert.Contains(t, disco.ClaimsSupported, oidc.ClaimAudience)
assert.Contains(t, disco.ClaimsSupported, oidc.ClaimAuthorizedParty)
assert.Contains(t, disco.ClaimsSupported, oidc.ClaimClientIdentifier)
assert.Contains(t, disco.ClaimsSupported, oidc.ClaimExpirationTime)
assert.Contains(t, disco.ClaimsSupported, oidc.ClaimIssuedAt)
assert.Contains(t, disco.ClaimsSupported, oidc.ClaimIssuer)
assert.Contains(t, disco.ClaimsSupported, oidc.ClaimJWTID)
assert.Contains(t, disco.ClaimsSupported, oidc.ClaimRequestedAt)
assert.Contains(t, disco.ClaimsSupported, oidc.ClaimSubject)
assert.Contains(t, disco.ClaimsSupported, oidc.ClaimAuthenticationTime)
assert.Contains(t, disco.ClaimsSupported, oidc.ClaimNonce)
assert.Contains(t, disco.ClaimsSupported, oidc.ClaimPreferredEmail)
assert.Contains(t, disco.ClaimsSupported, oidc.ClaimEmailVerified)
assert.Contains(t, disco.ClaimsSupported, oidc.ClaimEmailAlts)
assert.Contains(t, disco.ClaimsSupported, oidc.ClaimGroups)
assert.Contains(t, disco.ClaimsSupported, oidc.ClaimPreferredUsername)
assert.Contains(t, disco.ClaimsSupported, oidc.ClaimFullName)
}
func TestNewOpenIDConnectProvider_GetOpenIDConnectWellKnownConfigurationWithPlainPKCE(t *testing.T) {
provider := oidc.NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: keyRSA2048,
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
EnablePKCEPlainChallenge: true,
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "a-client",
Secret: tOpenIDConnectPlainTextClientSecret,
Policy: onefactor,
RedirectURIs: []string{
"https://google.com",
},
},
},
}, nil, nil)
require.NotNil(t, provider)
disco := provider.GetOpenIDConnectWellKnownConfiguration(examplecom)
require.Len(t, disco.CodeChallengeMethodsSupported, 2)
assert.Equal(t, oidc.PKCEChallengeMethodSHA256, disco.CodeChallengeMethodsSupported[0])
assert.Equal(t, oidc.PKCEChallengeMethodPlain, disco.CodeChallengeMethodsSupported[1])
}
func TestNewOpenIDConnectWellKnownConfiguration_Copy(t *testing.T) {
config := &oidc.OpenIDConnectWellKnownConfiguration{
OAuth2WellKnownConfiguration: oidc.OAuth2WellKnownConfiguration{
CommonDiscoveryOptions: oidc.CommonDiscoveryOptions{
Issuer: "https://example.com",
JWKSURI: "https://example.com/jwks.json",
AuthorizationEndpoint: "",
TokenEndpoint: "",
SubjectTypesSupported: nil,
ResponseTypesSupported: nil,
GrantTypesSupported: nil,
ResponseModesSupported: nil,
ScopesSupported: nil,
ClaimsSupported: nil,
UILocalesSupported: nil,
TokenEndpointAuthMethodsSupported: nil,
TokenEndpointAuthSigningAlgValuesSupported: nil,
ServiceDocumentation: "",
OPPolicyURI: "",
OPTOSURI: "",
SignedMetadata: "",
},
OAuth2DiscoveryOptions: oidc.OAuth2DiscoveryOptions{
IntrospectionEndpoint: "",
RevocationEndpoint: "",
RegistrationEndpoint: "",
IntrospectionEndpointAuthMethodsSupported: nil,
RevocationEndpointAuthMethodsSupported: nil,
RevocationEndpointAuthSigningAlgValuesSupported: nil,
IntrospectionEndpointAuthSigningAlgValuesSupported: nil,
CodeChallengeMethodsSupported: nil,
},
OAuth2DeviceAuthorizationGrantDiscoveryOptions: &oidc.OAuth2DeviceAuthorizationGrantDiscoveryOptions{
DeviceAuthorizationEndpoint: "",
},
OAuth2MutualTLSClientAuthenticationDiscoveryOptions: &oidc.OAuth2MutualTLSClientAuthenticationDiscoveryOptions{
TLSClientCertificateBoundAccessTokens: false,
MutualTLSEndpointAliases: oidc.OAuth2MutualTLSClientAuthenticationAliasesDiscoveryOptions{
AuthorizationEndpoint: "",
TokenEndpoint: "",
IntrospectionEndpoint: "",
RevocationEndpoint: "",
EndSessionEndpoint: "",
UserinfoEndpoint: "",
BackChannelAuthenticationEndpoint: "",
FederationRegistrationEndpoint: "",
PushedAuthorizationRequestEndpoint: "",
RegistrationEndpoint: "",
},
},
OAuth2IssuerIdentificationDiscoveryOptions: &oidc.OAuth2IssuerIdentificationDiscoveryOptions{
AuthorizationResponseIssuerParameterSupported: false,
},
OAuth2JWTIntrospectionResponseDiscoveryOptions: &oidc.OAuth2JWTIntrospectionResponseDiscoveryOptions{
IntrospectionSigningAlgValuesSupported: nil,
IntrospectionEncryptionAlgValuesSupported: nil,
IntrospectionEncryptionEncValuesSupported: nil,
},
OAuth2JWTSecuredAuthorizationRequestDiscoveryOptions: &oidc.OAuth2JWTSecuredAuthorizationRequestDiscoveryOptions{
RequireSignedRequestObject: false,
},
OAuth2PushedAuthorizationDiscoveryOptions: &oidc.OAuth2PushedAuthorizationDiscoveryOptions{
PushedAuthorizationRequestEndpoint: "",
RequirePushedAuthorizationRequests: false,
},
},
OpenIDConnectDiscoveryOptions: oidc.OpenIDConnectDiscoveryOptions{
UserinfoEndpoint: "",
IDTokenSigningAlgValuesSupported: nil,
UserinfoSigningAlgValuesSupported: nil,
RequestObjectSigningAlgValuesSupported: nil,
IDTokenEncryptionAlgValuesSupported: nil,
UserinfoEncryptionAlgValuesSupported: nil,
RequestObjectEncryptionAlgValuesSupported: nil,
IDTokenEncryptionEncValuesSupported: nil,
UserinfoEncryptionEncValuesSupported: nil,
RequestObjectEncryptionEncValuesSupported: nil,
ACRValuesSupported: nil,
DisplayValuesSupported: nil,
ClaimTypesSupported: nil,
ClaimLocalesSupported: nil,
RequestParameterSupported: false,
RequestURIParameterSupported: false,
RequireRequestURIRegistration: false,
ClaimsParameterSupported: false,
},
OpenIDConnectFrontChannelLogoutDiscoveryOptions: &oidc.OpenIDConnectFrontChannelLogoutDiscoveryOptions{
FrontChannelLogoutSupported: false,
FrontChannelLogoutSessionSupported: false,
},
OpenIDConnectBackChannelLogoutDiscoveryOptions: &oidc.OpenIDConnectBackChannelLogoutDiscoveryOptions{
BackChannelLogoutSupported: false,
BackChannelLogoutSessionSupported: false,
},
OpenIDConnectSessionManagementDiscoveryOptions: &oidc.OpenIDConnectSessionManagementDiscoveryOptions{
CheckSessionIFrame: "",
},
OpenIDConnectRPInitiatedLogoutDiscoveryOptions: &oidc.OpenIDConnectRPInitiatedLogoutDiscoveryOptions{
EndSessionEndpoint: "",
},
OpenIDConnectPromptCreateDiscoveryOptions: &oidc.OpenIDConnectPromptCreateDiscoveryOptions{
PromptValuesSupported: nil,
},
OpenIDConnectClientInitiatedBackChannelAuthFlowDiscoveryOptions: &oidc.OpenIDConnectClientInitiatedBackChannelAuthFlowDiscoveryOptions{
BackChannelAuthenticationEndpoint: "",
BackChannelTokenDeliveryModesSupported: nil,
BackChannelAuthRequestSigningAlgValuesSupported: nil,
BackChannelUserCodeParameterSupported: false,
},
OpenIDConnectJWTSecuredAuthorizationResponseModeDiscoveryOptions: &oidc.OpenIDConnectJWTSecuredAuthorizationResponseModeDiscoveryOptions{
AuthorizationSigningAlgValuesSupported: nil,
AuthorizationEncryptionAlgValuesSupported: nil,
AuthorizationEncryptionEncValuesSupported: nil,
},
OpenIDFederationDiscoveryOptions: &oidc.OpenIDFederationDiscoveryOptions{
FederationRegistrationEndpoint: "",
ClientRegistrationTypesSupported: nil,
RequestAuthenticationMethodsSupported: nil,
RequestAuthenticationSigningAlgValuesSupproted: nil,
},
}
x := config.Copy()
assert.Equal(t, config, &x)
y := config.OAuth2WellKnownConfiguration.Copy()
assert.Equal(t, config.OAuth2WellKnownConfiguration, y)
}

View File

@ -1,49 +0,0 @@
package oidc
import (
"context"
"github.com/go-crypt/crypt"
"github.com/go-crypt/crypt/algorithm"
"github.com/go-crypt/crypt/algorithm/plaintext"
)
// NewHasher returns a new Hasher.
func NewHasher() (hasher *Hasher, err error) {
hasher = &Hasher{}
if hasher.decoder, err = crypt.NewDefaultDecoder(); err != nil {
return nil, err
}
if err = plaintext.RegisterDecoderPlainText(hasher.decoder); err != nil {
return nil, err
}
return hasher, nil
}
// Hasher implements the fosite.Hasher interface and adaptively compares hashes.
type Hasher struct {
decoder algorithm.DecoderRegister
}
// Compare compares the hash with the data and returns an error if they don't match.
func (h Hasher) Compare(_ context.Context, hash, data []byte) (err error) {
var digest algorithm.Digest
if digest, err = h.decoder.Decode(string(hash)); err != nil {
return err
}
if digest.MatchBytes(data) {
return nil
}
return errPasswordsDoNotMatch
}
// Hash creates a new hash from data.
func (h Hasher) Hash(_ context.Context, data []byte) (hash []byte, err error) {
return data, nil
}

View File

@ -1,61 +0,0 @@
package oidc
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestShouldNotRaiseErrorOnEqualPasswordsPlainText(t *testing.T) {
hasher, err := NewHasher()
require.NoError(t, err)
a := []byte("$plaintext$abc")
b := []byte("abc")
ctx := context.Background()
assert.NoError(t, hasher.Compare(ctx, a, b))
}
func TestShouldNotRaiseErrorOnEqualPasswordsPlainTextWithSeparator(t *testing.T) {
hasher, err := NewHasher()
require.NoError(t, err)
a := []byte("$plaintext$abc$123")
b := []byte("abc$123")
ctx := context.Background()
assert.NoError(t, hasher.Compare(ctx, a, b))
}
func TestShouldRaiseErrorOnNonEqualPasswordsPlainText(t *testing.T) {
hasher, err := NewHasher()
require.NoError(t, err)
a := []byte("$plaintext$abc")
b := []byte("abcd")
ctx := context.Background()
assert.Equal(t, errPasswordsDoNotMatch, hasher.Compare(ctx, a, b))
}
func TestShouldHashPassword(t *testing.T) {
hasher := Hasher{}
data := []byte("abc")
ctx := context.Background()
hash, err := hasher.Hash(ctx, data)
assert.NoError(t, err)
assert.Equal(t, data, hash)
}

View File

@ -26,7 +26,7 @@ func NewKeyManager(config *schema.OpenIDConnectConfiguration) (manager *KeyManag
algs: map[string]*JWK{},
}
for _, sjwk := range config.IssuerJWKS {
for _, sjwk := range config.IssuerPrivateKeys {
jwk := NewJWK(sjwk)
manager.kids[sjwk.KeyID] = jwk
@ -47,7 +47,13 @@ type KeyManager struct {
algs map[string]*JWK
}
func (m *KeyManager) GetKIDFromAlgStrict(ctx context.Context, alg string) (kid string, err error) {
// GetKeyID returns the default key id.
func (m *KeyManager) GetKeyID(ctx context.Context) string {
return m.kid
}
// GetKeyIDFromAlgStrict returns the key id given an alg or an error if it doesn't exist.
func (m *KeyManager) GetKeyIDFromAlgStrict(ctx context.Context, alg string) (kid string, err error) {
if jwks, ok := m.algs[alg]; ok {
return jwks.kid, nil
}
@ -55,7 +61,8 @@ func (m *KeyManager) GetKIDFromAlgStrict(ctx context.Context, alg string) (kid s
return "", fmt.Errorf("alg not found")
}
func (m *KeyManager) GetKIDFromAlg(ctx context.Context, alg string) string {
// GetKeyIDFromAlg returns the key id given an alg or the default if it doesn't exist.
func (m *KeyManager) GetKeyIDFromAlg(ctx context.Context, alg string) string {
if jwks, ok := m.algs[alg]; ok {
return jwks.kid
}
@ -63,6 +70,7 @@ func (m *KeyManager) GetKIDFromAlg(ctx context.Context, alg string) string {
return m.kid
}
// GetByAlg returns the JWK given an alg or nil if it doesn't exist.
func (m *KeyManager) GetByAlg(ctx context.Context, alg string) *JWK {
if jwk, ok := m.algs[alg]; ok {
return jwk
@ -71,6 +79,7 @@ func (m *KeyManager) GetByAlg(ctx context.Context, alg string) *JWK {
return nil
}
// GetByKID returns the JWK given an key id or nil if it doesn't exist. If given a blank string it returns the default.
func (m *KeyManager) GetByKID(ctx context.Context, kid string) *JWK {
if kid == "" {
return m.kids[m.kid]
@ -83,6 +92,7 @@ func (m *KeyManager) GetByKID(ctx context.Context, kid string) *JWK {
return nil
}
// GetByHeader returns the JWK a JWT header with the appropriate kid value or returns an error.
func (m *KeyManager) GetByHeader(ctx context.Context, header fjwt.Mapper) (jwk *JWK, err error) {
var (
kid string
@ -104,6 +114,7 @@ func (m *KeyManager) GetByHeader(ctx context.Context, header fjwt.Mapper) (jwk *
return jwk, nil
}
// GetByTokenString does an invalidated decode of a token to get the header, then calls GetByHeader.
func (m *KeyManager) GetByTokenString(ctx context.Context, tokenString string) (jwk *JWK, err error) {
var (
token *jwt.Token
@ -116,6 +127,7 @@ func (m *KeyManager) GetByTokenString(ctx context.Context, tokenString string) (
return m.GetByHeader(ctx, &fjwt.Headers{Extra: token.Header})
}
// Set returns the *jose.JSONWebKeySet.
func (m *KeyManager) Set(ctx context.Context) *jose.JSONWebKeySet {
keys := make([]jose.JSONWebKey, 0, len(m.kids))
@ -130,6 +142,7 @@ func (m *KeyManager) Set(ctx context.Context) *jose.JSONWebKeySet {
}
}
// Generate implements the fosite jwt.Signer interface and automatically maps the underlying keys based on the JWK Header kid.
func (m *KeyManager) Generate(ctx context.Context, claims fjwt.MapClaims, header fjwt.Mapper) (tokenString string, sig string, err error) {
var jwk *JWK
@ -140,6 +153,7 @@ func (m *KeyManager) Generate(ctx context.Context, claims fjwt.MapClaims, header
return jwk.Strategy().Generate(ctx, claims, header)
}
// Validate implements the fosite jwt.Signer interface and automatically maps the underlying keys based on the JWK Header kid.
func (m *KeyManager) Validate(ctx context.Context, tokenString string) (sig string, err error) {
var jwk *JWK
@ -150,10 +164,12 @@ func (m *KeyManager) Validate(ctx context.Context, tokenString string) (sig stri
return jwk.Strategy().Validate(ctx, tokenString)
}
// Hash implements the fosite jwt.Signer interface.
func (m *KeyManager) Hash(ctx context.Context, in []byte) (sum []byte, err error) {
return m.GetByKID(ctx, "").Strategy().Hash(ctx, in)
}
// Decode implements the fosite jwt.Signer interface and automatically maps the underlying keys based on the JWK Header kid.
func (m *KeyManager) Decode(ctx context.Context, tokenString string) (token *fjwt.Token, err error) {
var jwk *JWK
@ -164,14 +180,58 @@ func (m *KeyManager) Decode(ctx context.Context, tokenString string) (token *fjw
return jwk.Strategy().Decode(ctx, tokenString)
}
// GetSignature implements the fosite jwt.Signer interface.
func (m *KeyManager) GetSignature(ctx context.Context, tokenString string) (sig string, err error) {
return getTokenSignature(tokenString)
}
// GetSigningMethodLength implements the fosite jwt.Signer interface.
func (m *KeyManager) GetSigningMethodLength(ctx context.Context) (size int) {
return m.GetByKID(ctx, "").Strategy().GetSigningMethodLength(ctx)
}
// NewPublicJSONWebKeySetFromSchemaJWK creates a *jose.JSONWebKeySet from a slice of schema.JWK.
func NewPublicJSONWebKeySetFromSchemaJWK(sjwks []schema.JWK) (jwks *jose.JSONWebKeySet) {
n := len(sjwks)
if n == 0 {
return nil
}
keys := make([]jose.JSONWebKey, n)
for i := 0; i < n; i++ {
jwk := jose.JSONWebKey{
KeyID: sjwks[i].KeyID,
Algorithm: sjwks[i].Algorithm,
Use: sjwks[i].Use,
Certificates: sjwks[i].CertificateChain.Certificates(),
}
switch key := sjwks[i].Key.(type) {
case *rsa.PublicKey:
jwk.Key = key
case rsa.PublicKey:
jwk.Key = &key
case *rsa.PrivateKey:
jwk.Key = key.PublicKey
case *ecdsa.PublicKey:
jwk.Key = key
case ecdsa.PublicKey:
jwk.Key = &key
case *ecdsa.PrivateKey:
jwk.Key = key.PublicKey
}
keys[i] = jwk
}
return &jose.JSONWebKeySet{
Keys: keys,
}
}
// NewJWK creates a *JWK f rom a schema.JWK.
func NewJWK(s schema.JWK) (jwk *JWK) {
jwk = &JWK{
kid: s.KeyID,
@ -198,6 +258,7 @@ func NewJWK(s schema.JWK) (jwk *JWK) {
return jwk
}
// JWK is a representation layer over the *jose.JSONWebKey for convenience.
type JWK struct {
kid string
use string
@ -210,16 +271,24 @@ type JWK struct {
thumbprint []byte
}
// GetSigningMethod returns the jwt.SigningMethod for this *JWK.
func (j *JWK) GetSigningMethod() jwt.SigningMethod {
return j.alg
}
// GetPrivateKey returns the Private Key for this *JWK.
func (j *JWK) GetPrivateKey(ctx context.Context) (any, error) {
return j.PrivateJWK(), nil
}
// KeyID returns the Key ID for this *JWK.
func (j *JWK) KeyID() string {
return j.kid
}
func (j *JWK) PrivateJWK() (jwk *jose.JSONWebKey) {
return &jose.JSONWebKey{
// DirectJWK directly returns the *JWK as a jose.JSONWebKey with the private key if appropriate.
func (j *JWK) DirectJWK() (jwk jose.JSONWebKey) {
return jose.JSONWebKey{
Key: j.key,
KeyID: j.kid,
Algorithm: j.alg.Alg(),
@ -230,10 +299,23 @@ func (j *JWK) PrivateJWK() (jwk *jose.JSONWebKey) {
}
}
func (j *JWK) JWK() (jwk jose.JSONWebKey) {
return j.PrivateJWK().Public()
// PrivateJWK directly returns the *JWK as a *jose.JSONWebKey with the private key if appropriate.
func (j *JWK) PrivateJWK() (jwk *jose.JSONWebKey) {
value := j.DirectJWK()
return &value
}
// JWK directly returns the *JWK as a jose.JSONWebKey specifically without the private key.
func (j *JWK) JWK() (jwk jose.JSONWebKey) {
if jwk = j.DirectJWK(); jwk.IsPublic() {
return jwk
}
return jwk.Public()
}
// Strategy returns the fosite jwt.Signer.
func (j *JWK) Strategy() (strategy fjwt.Signer) {
return &Signer{
hash: j.hash,
@ -250,6 +332,7 @@ type Signer struct {
GetPrivateKey fjwt.GetPrivateKeyFunc
}
// GetPublicKey returns the PublicKey for this Signer.
func (j *Signer) GetPublicKey(ctx context.Context) (key crypto.PublicKey, err error) {
var k any

View File

@ -1,7 +1,8 @@
package oidc
package oidc_test
import (
"context"
"crypto"
"encoding/json"
"fmt"
"testing"
@ -12,6 +13,7 @@ import (
"gopkg.in/square/go-jose.v2"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/oidc"
)
func TestKeyManager(t *testing.T) {
@ -19,82 +21,82 @@ func TestKeyManager(t *testing.T) {
Discovery: schema.OpenIDConnectDiscovery{
DefaultKeyID: "kid-RS256-sig",
},
IssuerJWKS: []schema.JWK{
IssuerPrivateKeys: []schema.JWK{
{
Use: KeyUseSignature,
Algorithm: SigningAlgRSAUsingSHA256,
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgRSAUsingSHA256,
Key: keyRSA2048,
CertificateChain: certRSA2048,
},
{
Use: KeyUseSignature,
Algorithm: SigningAlgRSAUsingSHA384,
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgRSAUsingSHA384,
Key: keyRSA2048,
CertificateChain: certRSA2048,
},
{
Use: KeyUseSignature,
Algorithm: SigningAlgRSAUsingSHA512,
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgRSAUsingSHA512,
Key: keyRSA4096,
CertificateChain: certRSA4096,
},
{
Use: KeyUseSignature,
Algorithm: SigningAlgRSAPSSUsingSHA256,
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgRSAPSSUsingSHA256,
Key: keyRSA2048,
CertificateChain: certRSA2048,
},
{
Use: KeyUseSignature,
Algorithm: SigningAlgRSAPSSUsingSHA384,
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgRSAPSSUsingSHA384,
Key: keyRSA2048,
CertificateChain: certRSA2048,
},
{
Use: KeyUseSignature,
Algorithm: SigningAlgRSAPSSUsingSHA512,
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgRSAPSSUsingSHA512,
Key: keyRSA4096,
CertificateChain: certRSA4096,
},
{
Use: KeyUseSignature,
Algorithm: SigningAlgECDSAUsingP256AndSHA256,
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgECDSAUsingP256AndSHA256,
Key: keyECDSAP256,
CertificateChain: certECDSAP256,
},
{
Use: KeyUseSignature,
Algorithm: SigningAlgECDSAUsingP384AndSHA384,
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgECDSAUsingP384AndSHA384,
Key: keyECDSAP384,
CertificateChain: certECDSAP384,
},
{
Use: KeyUseSignature,
Algorithm: SigningAlgECDSAUsingP521AndSHA512,
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgECDSAUsingP521AndSHA512,
Key: keyECDSAP521,
CertificateChain: certECDSAP521,
},
},
}
for i, key := range config.IssuerJWKS {
config.IssuerJWKS[i].KeyID = fmt.Sprintf("kid-%s-%s", key.Algorithm, key.Use)
for i, key := range config.IssuerPrivateKeys {
config.IssuerPrivateKeys[i].KeyID = fmt.Sprintf("kid-%s-%s", key.Algorithm, key.Use)
}
manager := NewKeyManager(config)
manager := oidc.NewKeyManager(config)
assert.NotNil(t, manager)
assert.Len(t, manager.kids, len(config.IssuerJWKS))
assert.Len(t, manager.algs, len(config.IssuerJWKS))
assert.Equal(t, "kid-RS256-sig", manager.kid)
ctx := context.Background()
assert.Equal(t, "kid-RS256-sig", manager.GetKeyID(ctx))
var (
jwk *JWK
err error
jwk *oidc.JWK
tokenString, sig string
sum []byte
token *fjwt.Token
err error
)
jwk = manager.GetByAlg(ctx, "notalg")
@ -107,7 +109,7 @@ func TestKeyManager(t *testing.T) {
assert.NotNil(t, jwk)
assert.Equal(t, config.Discovery.DefaultKeyID, jwk.KeyID())
jwk, err = manager.GetByHeader(ctx, &fjwt.Headers{Extra: map[string]any{JWTHeaderKeyIdentifier: "notalg"}})
jwk, err = manager.GetByHeader(ctx, &fjwt.Headers{Extra: map[string]any{oidc.JWTHeaderKeyIdentifier: "notalg"}})
assert.EqualError(t, err, "jwt header 'kid' with value 'notalg' does not match a managed jwk")
assert.Nil(t, jwk)
@ -119,17 +121,17 @@ func TestKeyManager(t *testing.T) {
assert.EqualError(t, err, "jwt header was nil")
assert.Nil(t, jwk)
kid, err := manager.GetKIDFromAlgStrict(ctx, "notalg")
kid, err := manager.GetKeyIDFromAlgStrict(ctx, "notalg")
assert.EqualError(t, err, "alg not found")
assert.Equal(t, "", kid)
kid = manager.GetKIDFromAlg(ctx, "notalg")
kid = manager.GetKeyIDFromAlg(ctx, "notalg")
assert.Equal(t, config.Discovery.DefaultKeyID, kid)
set := manager.Set(ctx)
assert.NotNil(t, set)
assert.Len(t, set.Keys, len(config.IssuerJWKS))
assert.Len(t, set.Keys, len(config.IssuerPrivateKeys))
data, err := json.Marshal(&set)
assert.NoError(t, err)
@ -139,9 +141,32 @@ func TestKeyManager(t *testing.T) {
assert.NoError(t, json.Unmarshal(data, &out))
assert.Equal(t, *set, out)
for _, alg := range []string{SigningAlgRSAUsingSHA256, SigningAlgRSAUsingSHA384, SigningAlgRSAPSSUsingSHA512, SigningAlgRSAPSSUsingSHA256, SigningAlgRSAPSSUsingSHA384, SigningAlgRSAPSSUsingSHA512, SigningAlgECDSAUsingP256AndSHA256, SigningAlgECDSAUsingP384AndSHA384, SigningAlgECDSAUsingP521AndSHA512} {
jwk, err = manager.GetByTokenString(ctx, badTokenString)
assert.EqualError(t, err, "token contains an invalid number of segments")
assert.Nil(t, jwk)
tokenString, sig, err = manager.Generate(ctx, nil, nil)
assert.EqualError(t, err, "error getting jwk from header: jwt header was nil")
assert.Equal(t, "", tokenString)
assert.Equal(t, "", sig)
sig, err = manager.Validate(ctx, badTokenString)
assert.EqualError(t, err, "error getting jwk from token string: token contains an invalid number of segments")
assert.Equal(t, "", sig)
token, err = manager.Decode(ctx, badTokenString)
assert.EqualError(t, err, "error getting jwk from token string: token contains an invalid number of segments")
assert.Nil(t, token)
sum, err = manager.Hash(ctx, []byte("abc"))
assert.NoError(t, err)
assert.Equal(t, "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", fmt.Sprintf("%x", sum))
assert.Equal(t, crypto.SHA256.Size(), manager.GetSigningMethodLength(ctx))
for _, alg := range []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgRSAUsingSHA384, oidc.SigningAlgRSAPSSUsingSHA512, oidc.SigningAlgRSAPSSUsingSHA256, oidc.SigningAlgRSAPSSUsingSHA384, oidc.SigningAlgRSAPSSUsingSHA512, oidc.SigningAlgECDSAUsingP256AndSHA256, oidc.SigningAlgECDSAUsingP384AndSHA384, oidc.SigningAlgECDSAUsingP521AndSHA512} {
t.Run(alg, func(t *testing.T) {
expectedKID := fmt.Sprintf("kid-%s-%s", alg, KeyUseSignature)
expectedKID := fmt.Sprintf("kid-%s-%s", alg, oidc.KeyUseSignature)
t.Run("ShouldGetCorrectKey", func(t *testing.T) {
jwk = manager.GetByKID(ctx, expectedKID)
@ -151,17 +176,17 @@ func TestKeyManager(t *testing.T) {
jwk = manager.GetByAlg(ctx, alg)
assert.NotNil(t, jwk)
assert.Equal(t, alg, jwk.alg.Alg())
assert.Equal(t, alg, jwk.GetSigningMethod().Alg())
assert.Equal(t, expectedKID, jwk.KeyID())
kid, err = manager.GetKIDFromAlgStrict(ctx, alg)
kid, err = manager.GetKeyIDFromAlgStrict(ctx, alg)
assert.NoError(t, err)
assert.Equal(t, expectedKID, kid)
kid = manager.GetKIDFromAlg(ctx, alg)
kid = manager.GetKeyIDFromAlg(ctx, alg)
assert.Equal(t, expectedKID, kid)
jwk, err = manager.GetByHeader(ctx, &fjwt.Headers{Extra: map[string]any{JWTHeaderKeyIdentifier: expectedKID}})
jwk, err = manager.GetByHeader(ctx, &fjwt.Headers{Extra: map[string]any{oidc.JWTHeaderKeyIdentifier: expectedKID}})
assert.NoError(t, err)
assert.NotNil(t, jwk)
@ -169,10 +194,9 @@ func TestKeyManager(t *testing.T) {
})
t.Run("ShouldUseCorrectSigner", func(t *testing.T) {
var tokenString, sig, sigb string
var token *fjwt.Token
var sigb string
tokenString, sig, err = manager.Generate(ctx, fjwt.MapClaims{}, &fjwt.Headers{Extra: map[string]any{JWTHeaderKeyIdentifier: expectedKID}})
tokenString, sig, err = manager.Generate(ctx, fjwt.MapClaims{}, &fjwt.Headers{Extra: map[string]any{oidc.JWTHeaderKeyIdentifier: expectedKID}})
assert.NoError(t, err)
sigb, err = manager.GetSignature(ctx, tokenString)
@ -185,10 +209,9 @@ func TestKeyManager(t *testing.T) {
token, err = manager.Decode(ctx, tokenString)
assert.NoError(t, err)
assert.Equal(t, expectedKID, token.Header[JWTHeaderKeyIdentifier])
assert.Equal(t, expectedKID, token.Header[oidc.JWTHeaderKeyIdentifier])
jwk, err = manager.GetByTokenString(ctx, tokenString)
assert.NoError(t, err)
sigb, err = jwk.Strategy().Validate(ctx, tokenString)
@ -206,8 +229,8 @@ func TestJWKFunctionality(t *testing.T) {
{
schema.JWK{
KeyID: "rsa2048-rs256",
Use: KeyUseSignature,
Algorithm: SigningAlgRSAUsingSHA256,
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgRSAUsingSHA256,
Key: keyRSA2048,
CertificateChain: certRSA2048,
},
@ -215,8 +238,8 @@ func TestJWKFunctionality(t *testing.T) {
{
schema.JWK{
KeyID: "rsa2048-rs384",
Use: KeyUseSignature,
Algorithm: SigningAlgRSAUsingSHA384,
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgRSAUsingSHA384,
Key: keyRSA2048,
CertificateChain: certRSA2048,
},
@ -224,8 +247,8 @@ func TestJWKFunctionality(t *testing.T) {
{
schema.JWK{
KeyID: "rsa2048-rs512",
Use: KeyUseSignature,
Algorithm: SigningAlgRSAUsingSHA512,
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgRSAUsingSHA512,
Key: keyRSA2048,
CertificateChain: certRSA2048,
},
@ -233,8 +256,8 @@ func TestJWKFunctionality(t *testing.T) {
{
schema.JWK{
KeyID: "rsa4096-rs256",
Use: KeyUseSignature,
Algorithm: SigningAlgRSAUsingSHA256,
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgRSAUsingSHA256,
Key: keyRSA4096,
CertificateChain: certRSA4096,
},
@ -242,8 +265,8 @@ func TestJWKFunctionality(t *testing.T) {
{
schema.JWK{
KeyID: "rsa4096-rs384",
Use: KeyUseSignature,
Algorithm: SigningAlgRSAUsingSHA384,
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgRSAUsingSHA384,
Key: keyRSA4096,
CertificateChain: certRSA4096,
},
@ -251,8 +274,8 @@ func TestJWKFunctionality(t *testing.T) {
{
schema.JWK{
KeyID: "rsa4096-rs512",
Use: KeyUseSignature,
Algorithm: SigningAlgRSAUsingSHA512,
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgRSAUsingSHA512,
Key: keyRSA4096,
CertificateChain: certRSA4096,
},
@ -260,8 +283,8 @@ func TestJWKFunctionality(t *testing.T) {
{
schema.JWK{
KeyID: "rsa2048-rs256",
Use: KeyUseSignature,
Algorithm: SigningAlgRSAPSSUsingSHA256,
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgRSAPSSUsingSHA256,
Key: keyRSA2048,
CertificateChain: certRSA2048,
},
@ -269,8 +292,8 @@ func TestJWKFunctionality(t *testing.T) {
{
schema.JWK{
KeyID: "rsa2048-ps384",
Use: KeyUseSignature,
Algorithm: SigningAlgRSAPSSUsingSHA384,
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgRSAPSSUsingSHA384,
Key: keyRSA2048,
CertificateChain: certRSA2048,
},
@ -278,8 +301,8 @@ func TestJWKFunctionality(t *testing.T) {
{
schema.JWK{
KeyID: "rsa2048-ps512",
Use: KeyUseSignature,
Algorithm: SigningAlgRSAPSSUsingSHA512,
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgRSAPSSUsingSHA512,
Key: keyRSA2048,
CertificateChain: certRSA2048,
},
@ -287,8 +310,8 @@ func TestJWKFunctionality(t *testing.T) {
{
schema.JWK{
KeyID: "rsa4096-ps256",
Use: KeyUseSignature,
Algorithm: SigningAlgRSAPSSUsingSHA256,
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgRSAPSSUsingSHA256,
Key: keyRSA4096,
CertificateChain: certRSA4096,
},
@ -296,8 +319,8 @@ func TestJWKFunctionality(t *testing.T) {
{
schema.JWK{
KeyID: "rsa4096-ps384",
Use: KeyUseSignature,
Algorithm: SigningAlgRSAPSSUsingSHA384,
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgRSAPSSUsingSHA384,
Key: keyRSA4096,
CertificateChain: certRSA4096,
},
@ -305,8 +328,8 @@ func TestJWKFunctionality(t *testing.T) {
{
schema.JWK{
KeyID: "rsa4096-ps512",
Use: KeyUseSignature,
Algorithm: SigningAlgRSAPSSUsingSHA512,
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgRSAPSSUsingSHA512,
Key: keyRSA4096,
CertificateChain: certRSA4096,
},
@ -314,8 +337,8 @@ func TestJWKFunctionality(t *testing.T) {
{
schema.JWK{
KeyID: "ecdsaP256",
Use: KeyUseSignature,
Algorithm: SigningAlgECDSAUsingP256AndSHA256,
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgECDSAUsingP256AndSHA256,
Key: keyECDSAP256,
CertificateChain: certECDSAP256,
},
@ -323,8 +346,8 @@ func TestJWKFunctionality(t *testing.T) {
{
schema.JWK{
KeyID: "ecdsaP384",
Use: KeyUseSignature,
Algorithm: SigningAlgECDSAUsingP384AndSHA384,
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgECDSAUsingP384AndSHA384,
Key: keyECDSAP384,
CertificateChain: certECDSAP384,
},
@ -332,8 +355,8 @@ func TestJWKFunctionality(t *testing.T) {
{
schema.JWK{
KeyID: "ecdsaP521",
Use: KeyUseSignature,
Algorithm: SigningAlgECDSAUsingP521AndSHA512,
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgECDSAUsingP521AndSHA512,
Key: keyECDSAP521,
CertificateChain: certECDSAP521,
},
@ -344,24 +367,28 @@ func TestJWKFunctionality(t *testing.T) {
t.Run(tc.have.KeyID, func(t *testing.T) {
t.Run("Generating", func(t *testing.T) {
var (
jwk *JWK
jwk *oidc.JWK
)
ctx := context.Background()
jwk = NewJWK(tc.have)
jwk = oidc.NewJWK(tc.have)
signer := jwk.Strategy()
claims := fjwt.MapClaims{}
header := &fjwt.Headers{
Extra: map[string]any{
"kid": jwk.kid,
oidc.JWTHeaderKeyIdentifier: jwk.KeyID(),
},
}
tokenString, sig, err := signer.Generate(ctx, claims, header)
tokenString, sig, err := signer.Generate(ctx, nil, nil)
assert.EqualError(t, err, "either claims or header is nil")
assert.Equal(t, "", tokenString)
assert.Equal(t, "", sig)
tokenString, sig, err = signer.Generate(ctx, claims, header)
assert.NoError(t, err)
assert.NotEqual(t, "", tokenString)
assert.NotEqual(t, "", sig)
@ -376,7 +403,7 @@ func TestJWKFunctionality(t *testing.T) {
fmt.Println(tokenString)
assert.True(t, token.Valid())
assert.Equal(t, jwk.alg.Alg(), string(token.Method))
assert.Equal(t, jwk.GetSigningMethod().Alg(), string(token.Method))
sigv, err := signer.Validate(ctx, tokenString)
assert.NoError(t, err)
@ -385,19 +412,19 @@ func TestJWKFunctionality(t *testing.T) {
t.Run("Marshalling", func(t *testing.T) {
var (
jwk *JWK
jwk *oidc.JWK
out jose.JSONWebKey
data []byte
err error
)
jwk = NewJWK(tc.have)
jwk = oidc.NewJWK(tc.have)
strategy := jwk.Strategy()
assert.NotNil(t, strategy)
signer, ok := strategy.(*Signer)
signer, ok := strategy.(*oidc.Signer)
require.True(t, ok)

View File

@ -0,0 +1,37 @@
package oidc
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestValidateToken(t *testing.T) {
sig, err := validateToken("none", nil)
assert.Equal(t, "", sig)
assert.EqualError(t, err, "square/go-jose: compact JWS format must have three parts")
}
func TestGetTokenSignature(t *testing.T) {
sig, err := getTokenSignature("abc.123")
assert.Equal(t, "", sig)
assert.EqualError(t, err, "header, body and signature must all be set")
}
func TestAssign(t *testing.T) {
a := map[string]any{
"a": "valuea",
"c": "valuea",
}
b := map[string]any{
"b": "valueb",
"c": "valueb",
}
c := assign(a, b)
assert.Equal(t, "valuea", c["a"])
assert.Equal(t, "valueb", c["b"])
assert.Equal(t, "valuea", c["c"])
}

View File

@ -1,7 +1,6 @@
package oidc
package oidc_test
import (
"encoding/json"
"net/url"
"testing"
@ -9,16 +8,17 @@ import (
"github.com/stretchr/testify/require"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/oidc"
)
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_NotConfigured(t *testing.T) {
provider := NewOpenIDConnectProvider(nil, nil, nil)
provider := oidc.NewOpenIDConnectProvider(nil, nil, nil)
assert.Nil(t, provider)
}
func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing.T) {
provider := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
provider := oidc.NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: keyRSA2048,
EnablePKCEPlainChallenge: true,
@ -26,7 +26,7 @@ func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: myclient,
Secret: MustDecodeSecret(badsecret),
Secret: tOpenIDConnectPlainTextClientSecret,
SectorIdentifier: url.URL{Host: examplecomsid},
Policy: onefactor,
RedirectURIs: []string{
@ -41,23 +41,23 @@ func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing
disco := provider.GetOpenIDConnectWellKnownConfiguration(examplecom)
assert.Len(t, disco.SubjectTypesSupported, 2)
assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePublic)
assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePairwise)
assert.Contains(t, disco.SubjectTypesSupported, oidc.SubjectTypePublic)
assert.Contains(t, disco.SubjectTypesSupported, oidc.SubjectTypePairwise)
assert.Len(t, disco.CodeChallengeMethodsSupported, 2)
assert.Contains(t, disco.CodeChallengeMethodsSupported, PKCEChallengeMethodSHA256)
assert.Contains(t, disco.CodeChallengeMethodsSupported, PKCEChallengeMethodSHA256)
assert.Contains(t, disco.CodeChallengeMethodsSupported, oidc.PKCEChallengeMethodSHA256)
assert.Contains(t, disco.CodeChallengeMethodsSupported, oidc.PKCEChallengeMethodSHA256)
}
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *testing.T) {
provider := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
provider := oidc.NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: keyRSA2048,
HMACSecret: badhmac,
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "a-client",
Secret: MustDecodeSecret("$plaintext$a-client-secret"),
Secret: tOpenIDConnectPlainTextClientSecret,
Policy: onefactor,
RedirectURIs: []string{
"https://google.com",
@ -66,16 +66,16 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *tes
{
ID: "b-client",
Description: "Normal Description",
Secret: MustDecodeSecret("$plaintext$b-client-secret"),
Secret: tOpenIDConnectPlainTextClientSecret,
Policy: twofactor,
RedirectURIs: []string{
"https://google.com",
},
Scopes: []string{
ScopeGroups,
oidc.ScopeGroups,
},
GrantTypes: []string{
GrantTypeRefreshToken,
oidc.GrantTypeRefreshToken,
},
ResponseTypes: []string{
"token",
@ -87,285 +87,3 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *tes
assert.NotNil(t, provider)
}
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnownConfiguration(t *testing.T) {
provider := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: keyRSA2048,
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "a-client",
Secret: MustDecodeSecret("$plaintext$a-client-secret"),
Policy: onefactor,
RedirectURIs: []string{
"https://google.com",
},
},
},
}, nil, nil)
require.NotNil(t, provider)
disco := provider.GetOpenIDConnectWellKnownConfiguration(examplecom)
assert.Equal(t, examplecom, disco.Issuer)
assert.Equal(t, "https://example.com/jwks.json", disco.JWKSURI)
assert.Equal(t, "https://example.com/api/oidc/authorization", disco.AuthorizationEndpoint)
assert.Equal(t, "https://example.com/api/oidc/token", disco.TokenEndpoint)
assert.Equal(t, "https://example.com/api/oidc/userinfo", disco.UserinfoEndpoint)
assert.Equal(t, "https://example.com/api/oidc/introspection", disco.IntrospectionEndpoint)
assert.Equal(t, "https://example.com/api/oidc/revocation", disco.RevocationEndpoint)
assert.Equal(t, "", disco.RegistrationEndpoint)
assert.Len(t, disco.CodeChallengeMethodsSupported, 1)
assert.Contains(t, disco.CodeChallengeMethodsSupported, PKCEChallengeMethodSHA256)
assert.Len(t, disco.ScopesSupported, 5)
assert.Contains(t, disco.ScopesSupported, ScopeOpenID)
assert.Contains(t, disco.ScopesSupported, ScopeOfflineAccess)
assert.Contains(t, disco.ScopesSupported, ScopeProfile)
assert.Contains(t, disco.ScopesSupported, ScopeGroups)
assert.Contains(t, disco.ScopesSupported, ScopeEmail)
assert.Len(t, disco.ResponseModesSupported, 3)
assert.Contains(t, disco.ResponseModesSupported, ResponseModeFormPost)
assert.Contains(t, disco.ResponseModesSupported, ResponseModeQuery)
assert.Contains(t, disco.ResponseModesSupported, ResponseModeFragment)
assert.Len(t, disco.SubjectTypesSupported, 2)
assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePublic)
assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePairwise)
assert.Len(t, disco.ResponseTypesSupported, 7)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeAuthorizationCodeFlow)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeImplicitFlowIDToken)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeImplicitFlowToken)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeImplicitFlowBoth)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowIDToken)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowToken)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowBoth)
assert.Len(t, disco.TokenEndpointAuthMethodsSupported, 4)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretBasic)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretPost)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretJWT)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodNone)
assert.Len(t, disco.RevocationEndpointAuthMethodsSupported, 4)
assert.Contains(t, disco.RevocationEndpointAuthMethodsSupported, ClientAuthMethodClientSecretBasic)
assert.Contains(t, disco.RevocationEndpointAuthMethodsSupported, ClientAuthMethodClientSecretPost)
assert.Contains(t, disco.RevocationEndpointAuthMethodsSupported, ClientAuthMethodClientSecretJWT)
assert.Contains(t, disco.RevocationEndpointAuthMethodsSupported, ClientAuthMethodNone)
assert.Len(t, disco.IntrospectionEndpointAuthMethodsSupported, 2)
assert.Contains(t, disco.IntrospectionEndpointAuthMethodsSupported, ClientAuthMethodClientSecretBasic)
assert.Contains(t, disco.IntrospectionEndpointAuthMethodsSupported, ClientAuthMethodNone)
assert.Len(t, disco.GrantTypesSupported, 3)
assert.Contains(t, disco.GrantTypesSupported, GrantTypeAuthorizationCode)
assert.Contains(t, disco.GrantTypesSupported, GrantTypeRefreshToken)
assert.Contains(t, disco.GrantTypesSupported, GrantTypeImplicit)
assert.Len(t, disco.RevocationEndpointAuthSigningAlgValuesSupported, 3)
assert.Equal(t, disco.RevocationEndpointAuthSigningAlgValuesSupported[0], SigningAlgHMACUsingSHA256)
assert.Equal(t, disco.RevocationEndpointAuthSigningAlgValuesSupported[1], SigningAlgHMACUsingSHA384)
assert.Equal(t, disco.RevocationEndpointAuthSigningAlgValuesSupported[2], SigningAlgHMACUsingSHA512)
assert.Len(t, disco.TokenEndpointAuthSigningAlgValuesSupported, 3)
assert.Equal(t, disco.TokenEndpointAuthSigningAlgValuesSupported[0], SigningAlgHMACUsingSHA256)
assert.Equal(t, disco.TokenEndpointAuthSigningAlgValuesSupported[1], SigningAlgHMACUsingSHA384)
assert.Equal(t, disco.TokenEndpointAuthSigningAlgValuesSupported[2], SigningAlgHMACUsingSHA512)
assert.Len(t, disco.IDTokenSigningAlgValuesSupported, 1)
assert.Contains(t, disco.IDTokenSigningAlgValuesSupported, SigningAlgRSAUsingSHA256)
assert.Len(t, disco.UserinfoSigningAlgValuesSupported, 2)
assert.Equal(t, disco.UserinfoSigningAlgValuesSupported[0], SigningAlgRSAUsingSHA256)
assert.Equal(t, disco.UserinfoSigningAlgValuesSupported[1], SigningAlgNone)
require.Len(t, disco.RequestObjectSigningAlgValuesSupported, 2)
assert.Equal(t, SigningAlgRSAUsingSHA256, disco.RequestObjectSigningAlgValuesSupported[0])
assert.Equal(t, SigningAlgNone, disco.RequestObjectSigningAlgValuesSupported[1])
assert.Len(t, disco.ClaimsSupported, 18)
assert.Contains(t, disco.ClaimsSupported, ClaimAuthenticationMethodsReference)
assert.Contains(t, disco.ClaimsSupported, ClaimAudience)
assert.Contains(t, disco.ClaimsSupported, ClaimAuthorizedParty)
assert.Contains(t, disco.ClaimsSupported, ClaimClientIdentifier)
assert.Contains(t, disco.ClaimsSupported, ClaimExpirationTime)
assert.Contains(t, disco.ClaimsSupported, ClaimIssuedAt)
assert.Contains(t, disco.ClaimsSupported, ClaimIssuer)
assert.Contains(t, disco.ClaimsSupported, ClaimJWTID)
assert.Contains(t, disco.ClaimsSupported, ClaimRequestedAt)
assert.Contains(t, disco.ClaimsSupported, ClaimSubject)
assert.Contains(t, disco.ClaimsSupported, ClaimAuthenticationTime)
assert.Contains(t, disco.ClaimsSupported, ClaimNonce)
assert.Contains(t, disco.ClaimsSupported, ClaimPreferredEmail)
assert.Contains(t, disco.ClaimsSupported, ClaimEmailVerified)
assert.Contains(t, disco.ClaimsSupported, ClaimEmailAlts)
assert.Contains(t, disco.ClaimsSupported, ClaimGroups)
assert.Contains(t, disco.ClaimsSupported, ClaimPreferredUsername)
assert.Contains(t, disco.ClaimsSupported, ClaimFullName)
assert.Len(t, disco.PromptValuesSupported, 2)
assert.Contains(t, disco.PromptValuesSupported, PromptConsent)
assert.Contains(t, disco.PromptValuesSupported, PromptNone)
}
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfiguration(t *testing.T) {
provider := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: keyRSA2048,
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "a-client",
Secret: MustDecodeSecret("$plaintext$a-client-secret"),
Policy: onefactor,
RedirectURIs: []string{
"https://google.com",
},
},
},
}, nil, nil)
require.NotNil(t, provider)
disco := provider.GetOAuth2WellKnownConfiguration(examplecom)
assert.Equal(t, examplecom, disco.Issuer)
assert.Equal(t, "https://example.com/jwks.json", disco.JWKSURI)
assert.Equal(t, "https://example.com/api/oidc/authorization", disco.AuthorizationEndpoint)
assert.Equal(t, "https://example.com/api/oidc/token", disco.TokenEndpoint)
assert.Equal(t, "https://example.com/api/oidc/introspection", disco.IntrospectionEndpoint)
assert.Equal(t, "https://example.com/api/oidc/revocation", disco.RevocationEndpoint)
assert.Equal(t, "", disco.RegistrationEndpoint)
require.Len(t, disco.CodeChallengeMethodsSupported, 1)
assert.Equal(t, "S256", disco.CodeChallengeMethodsSupported[0])
assert.Len(t, disco.ScopesSupported, 5)
assert.Contains(t, disco.ScopesSupported, ScopeOpenID)
assert.Contains(t, disco.ScopesSupported, ScopeOfflineAccess)
assert.Contains(t, disco.ScopesSupported, ScopeProfile)
assert.Contains(t, disco.ScopesSupported, ScopeGroups)
assert.Contains(t, disco.ScopesSupported, ScopeEmail)
assert.Len(t, disco.ResponseModesSupported, 3)
assert.Contains(t, disco.ResponseModesSupported, ResponseModeFormPost)
assert.Contains(t, disco.ResponseModesSupported, ResponseModeQuery)
assert.Contains(t, disco.ResponseModesSupported, ResponseModeFragment)
assert.Len(t, disco.SubjectTypesSupported, 2)
assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePublic)
assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePairwise)
assert.Len(t, disco.ResponseTypesSupported, 7)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeAuthorizationCodeFlow)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeImplicitFlowIDToken)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeImplicitFlowToken)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeImplicitFlowBoth)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowIDToken)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowToken)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowBoth)
assert.Len(t, disco.TokenEndpointAuthMethodsSupported, 4)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretBasic)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretPost)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretJWT)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodNone)
assert.Len(t, disco.GrantTypesSupported, 3)
assert.Contains(t, disco.GrantTypesSupported, GrantTypeAuthorizationCode)
assert.Contains(t, disco.GrantTypesSupported, GrantTypeRefreshToken)
assert.Contains(t, disco.GrantTypesSupported, GrantTypeImplicit)
assert.Len(t, disco.ClaimsSupported, 18)
assert.Contains(t, disco.ClaimsSupported, ClaimAuthenticationMethodsReference)
assert.Contains(t, disco.ClaimsSupported, ClaimAudience)
assert.Contains(t, disco.ClaimsSupported, ClaimAuthorizedParty)
assert.Contains(t, disco.ClaimsSupported, ClaimClientIdentifier)
assert.Contains(t, disco.ClaimsSupported, ClaimExpirationTime)
assert.Contains(t, disco.ClaimsSupported, ClaimIssuedAt)
assert.Contains(t, disco.ClaimsSupported, ClaimIssuer)
assert.Contains(t, disco.ClaimsSupported, ClaimJWTID)
assert.Contains(t, disco.ClaimsSupported, ClaimRequestedAt)
assert.Contains(t, disco.ClaimsSupported, ClaimSubject)
assert.Contains(t, disco.ClaimsSupported, ClaimAuthenticationTime)
assert.Contains(t, disco.ClaimsSupported, ClaimNonce)
assert.Contains(t, disco.ClaimsSupported, ClaimPreferredEmail)
assert.Contains(t, disco.ClaimsSupported, ClaimEmailVerified)
assert.Contains(t, disco.ClaimsSupported, ClaimEmailAlts)
assert.Contains(t, disco.ClaimsSupported, ClaimGroups)
assert.Contains(t, disco.ClaimsSupported, ClaimPreferredUsername)
assert.Contains(t, disco.ClaimsSupported, ClaimFullName)
}
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnownConfigurationWithPlainPKCE(t *testing.T) {
provider := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: keyRSA2048,
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
EnablePKCEPlainChallenge: true,
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "a-client",
Secret: MustDecodeSecret("$plaintext$a-client-secret"),
Policy: onefactor,
RedirectURIs: []string{
"https://google.com",
},
},
},
}, nil, nil)
require.NotNil(t, provider)
disco := provider.GetOpenIDConnectWellKnownConfiguration(examplecom)
require.Len(t, disco.CodeChallengeMethodsSupported, 2)
assert.Equal(t, PKCEChallengeMethodSHA256, disco.CodeChallengeMethodsSupported[0])
assert.Equal(t, PKCEChallengeMethodPlain, disco.CodeChallengeMethodsSupported[1])
}
func TestNewOpenIDConnectProviderDiscovery(t *testing.T) {
provider := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: keyRSA2048,
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
EnablePKCEPlainChallenge: true,
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "a-client",
Secret: MustDecodeSecret("$plaintext$a-client-secret"),
Policy: onefactor,
RedirectURIs: []string{
"https://google.com",
},
},
},
}, nil, nil)
a := provider.GetOpenIDConnectWellKnownConfiguration("https://auth.example.com")
data, err := json.Marshal(&a)
assert.NoError(t, err)
b := OpenIDConnectWellKnownConfiguration{}
assert.NoError(t, json.Unmarshal(data, &b))
assert.Equal(t, a, b)
y := provider.GetOAuth2WellKnownConfiguration("https://auth.example.com")
data, err = json.Marshal(&y)
assert.NoError(t, err)
z := OAuth2WellKnownConfiguration{}
assert.NoError(t, json.Unmarshal(data, &z))
assert.Equal(t, y, z)
}

View File

@ -235,7 +235,7 @@ func (s *Store) CreatePKCERequestSession(ctx context.Context, signature string,
// DeletePKCERequestSession marks the authorization request for a given PKCE request as deleted.
// This implements a portion of pkce.PKCERequestStorage.
func (s *Store) DeletePKCERequestSession(ctx context.Context, signature string) (err error) {
return s.revokeSessionBySignature(ctx, storage.OAuth2SessionTypeAccessToken, signature)
return s.revokeSessionBySignature(ctx, storage.OAuth2SessionTypePKCEChallenge, signature)
}
// GetPKCERequestSession gets the authorization request for a given PKCE request.
@ -254,7 +254,7 @@ func (s *Store) CreateOpenIDConnectSession(ctx context.Context, authorizeCode st
// DeleteOpenIDConnectSession just implements the method required by fosite even though it's unused.
// This implements a portion of openid.OpenIDConnectRequestStorage.
func (s *Store) DeleteOpenIDConnectSession(ctx context.Context, authorizeCode string) (err error) {
return s.revokeSessionBySignature(ctx, storage.OAuth2SessionTypeAccessToken, authorizeCode)
return s.revokeSessionBySignature(ctx, storage.OAuth2SessionTypeOpenIDConnect, authorizeCode)
}
// GetOpenIDConnectSession returns error:

View File

@ -1,19 +1,30 @@
package oidc
package oidc_test
import (
"context"
"database/sql"
"encoding/json"
"fmt"
"testing"
"time"
"github.com/golang/mock/gomock"
"github.com/google/uuid"
"github.com/ory/fosite"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/authelia/authelia/v4/internal/authorization"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/mocks"
"github.com/authelia/authelia/v4/internal/model"
"github.com/authelia/authelia/v4/internal/oidc"
"github.com/authelia/authelia/v4/internal/storage"
)
func TestOpenIDConnectStore_GetClientPolicy(t *testing.T) {
s := NewStore(&schema.OpenIDConnectConfiguration{
s := oidc.NewStore(&schema.OpenIDConnectConfiguration{
IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: keyRSA2048,
Clients: []schema.OpenIDConnectClientConfiguration{
@ -21,15 +32,15 @@ func TestOpenIDConnectStore_GetClientPolicy(t *testing.T) {
ID: myclient,
Description: myclientdesc,
Policy: onefactor,
Scopes: []string{ScopeOpenID, ScopeProfile},
Secret: MustDecodeSecret("$plaintext$mysecret"),
Scopes: []string{oidc.ScopeOpenID, oidc.ScopeProfile},
Secret: tOpenIDConnectPlainTextClientSecret,
},
{
ID: "myotherclient",
Description: myclientdesc,
Policy: twofactor,
Scopes: []string{ScopeOpenID, ScopeProfile},
Secret: MustDecodeSecret("$plaintext$mysecret"),
Scopes: []string{oidc.ScopeOpenID, oidc.ScopeProfile},
Secret: tOpenIDConnectPlainTextClientSecret,
},
},
}, nil)
@ -45,7 +56,7 @@ func TestOpenIDConnectStore_GetClientPolicy(t *testing.T) {
}
func TestOpenIDConnectStore_GetInternalClient(t *testing.T) {
s := NewStore(&schema.OpenIDConnectConfiguration{
s := oidc.NewStore(&schema.OpenIDConnectConfiguration{
IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: keyRSA2048,
Clients: []schema.OpenIDConnectClientConfiguration{
@ -53,8 +64,8 @@ func TestOpenIDConnectStore_GetInternalClient(t *testing.T) {
ID: myclient,
Description: myclientdesc,
Policy: onefactor,
Scopes: []string{ScopeOpenID, ScopeProfile},
Secret: MustDecodeSecret("$plaintext$mysecret"),
Scopes: []string{oidc.ScopeOpenID, oidc.ScopeProfile},
Secret: tOpenIDConnectPlainTextClientSecret,
},
},
}, nil)
@ -76,11 +87,11 @@ func TestOpenIDConnectStore_GetInternalClient_ValidClient(t *testing.T) {
ID: id,
Description: myclientdesc,
Policy: onefactor,
Scopes: []string{ScopeOpenID, ScopeProfile},
Secret: MustDecodeSecret("$plaintext$mysecret"),
Scopes: []string{oidc.ScopeOpenID, oidc.ScopeProfile},
Secret: tOpenIDConnectPlainTextClientSecret,
}
s := NewStore(&schema.OpenIDConnectConfiguration{
s := oidc.NewStore(&schema.OpenIDConnectConfiguration{
IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: keyRSA2048,
Clients: []schema.OpenIDConnectClientConfiguration{c1},
@ -92,11 +103,11 @@ func TestOpenIDConnectStore_GetInternalClient_ValidClient(t *testing.T) {
assert.Equal(t, id, client.GetID())
assert.Equal(t, myclientdesc, client.GetDescription())
assert.Equal(t, fosite.Arguments(c1.Scopes), client.GetScopes())
assert.Equal(t, fosite.Arguments([]string{GrantTypeAuthorizationCode}), client.GetGrantTypes())
assert.Equal(t, fosite.Arguments([]string{ResponseTypeAuthorizationCodeFlow}), client.GetResponseTypes())
assert.Equal(t, fosite.Arguments([]string{oidc.GrantTypeAuthorizationCode}), client.GetGrantTypes())
assert.Equal(t, fosite.Arguments([]string{oidc.ResponseTypeAuthorizationCodeFlow}), client.GetResponseTypes())
assert.Equal(t, []string(nil), client.GetRedirectURIs())
assert.Equal(t, authorization.OneFactor, client.GetAuthorizationPolicy())
assert.Equal(t, "$plaintext$mysecret", client.GetSecret().Encode())
assert.Equal(t, "$plaintext$client-secret", client.GetSecret().Encode())
}
func TestOpenIDConnectStore_GetInternalClient_InvalidClient(t *testing.T) {
@ -104,11 +115,11 @@ func TestOpenIDConnectStore_GetInternalClient_InvalidClient(t *testing.T) {
ID: myclient,
Description: myclientdesc,
Policy: onefactor,
Scopes: []string{ScopeOpenID, ScopeProfile},
Secret: MustDecodeSecret("$plaintext$mysecret"),
Scopes: []string{oidc.ScopeOpenID, oidc.ScopeProfile},
Secret: tOpenIDConnectPlainTextClientSecret,
}
s := NewStore(&schema.OpenIDConnectConfiguration{
s := oidc.NewStore(&schema.OpenIDConnectConfiguration{
IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: keyRSA2048,
Clients: []schema.OpenIDConnectClientConfiguration{c1},
@ -120,7 +131,7 @@ func TestOpenIDConnectStore_GetInternalClient_InvalidClient(t *testing.T) {
}
func TestOpenIDConnectStore_IsValidClientID(t *testing.T) {
s := NewStore(&schema.OpenIDConnectConfiguration{
s := oidc.NewStore(&schema.OpenIDConnectConfiguration{
IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: keyRSA2048,
Clients: []schema.OpenIDConnectClientConfiguration{
@ -128,8 +139,8 @@ func TestOpenIDConnectStore_IsValidClientID(t *testing.T) {
ID: myclient,
Description: myclientdesc,
Policy: onefactor,
Scopes: []string{ScopeOpenID, ScopeProfile},
Secret: MustDecodeSecret("$plaintext$mysecret"),
Scopes: []string{oidc.ScopeOpenID, oidc.ScopeProfile},
Secret: tOpenIDConnectPlainTextClientSecret,
},
},
}, nil)
@ -140,3 +151,516 @@ func TestOpenIDConnectStore_IsValidClientID(t *testing.T) {
assert.True(t, validClient)
assert.False(t, invalidClient)
}
func TestStoreSuite(t *testing.T) {
suite.Run(t, &StoreSuite{})
}
type StoreSuite struct {
suite.Suite
ctx context.Context
ctrl *gomock.Controller
mock *mocks.MockStorage
store *oidc.Store
}
func (s *StoreSuite) SetupTest() {
s.ctx = context.Background()
s.ctrl = gomock.NewController(s.T())
s.mock = mocks.NewMockStorage(s.ctrl)
s.store = oidc.NewStore(&schema.OpenIDConnectConfiguration{
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "hs256",
Secret: tOpenIDConnectPBKDF2ClientSecret,
Policy: authorization.OneFactor.String(),
RedirectURIs: []string{
"https://client.example.com",
},
TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretJWT,
TokenEndpointAuthSigningAlg: oidc.SigningAlgHMACUsingSHA256,
},
}}, s.mock)
}
func (s *StoreSuite) TestGetSubject() {
s.T().Run("GenerateNew", func(t *testing.T) {
s.mock.
EXPECT().
LoadUserOpaqueIdentifierBySignature(s.ctx, "openid", "", "john").
Return(nil, nil)
s.mock.
EXPECT().
SaveUserOpaqueIdentifier(s.ctx, gomock.Any()).
Return(nil)
opaqueID, err := s.store.GetSubject(s.ctx, "", "john")
assert.NoError(t, err)
assert.NotEqual(t, uint32(0), opaqueID)
})
s.T().Run("ReturnDatabaseErrorOnLoad", func(t *testing.T) {
s.mock.
EXPECT().
LoadUserOpaqueIdentifierBySignature(s.ctx, "openid", "", "john").
Return(nil, fmt.Errorf("failed to load"))
opaqueID, err := s.store.GetSubject(s.ctx, "", "john")
assert.EqualError(t, err, "failed to load")
assert.Equal(t, uint32(0), opaqueID.ID())
})
s.T().Run("ReturnDatabaseErrorOnSave", func(t *testing.T) {
s.mock.
EXPECT().
LoadUserOpaqueIdentifierBySignature(s.ctx, "openid", "", "john").
Return(nil, nil)
s.mock.
EXPECT().
SaveUserOpaqueIdentifier(s.ctx, gomock.Any()).
Return(fmt.Errorf("failed to save"))
opaqueID, err := s.store.GetSubject(s.ctx, "", "john")
assert.EqualError(t, err, "failed to save")
assert.Equal(t, uint32(0), opaqueID.ID())
})
}
func (s *StoreSuite) TestTx() {
gomock.InOrder(
s.mock.EXPECT().BeginTX(s.ctx).Return(s.ctx, nil),
s.mock.EXPECT().Commit(s.ctx).Return(nil),
s.mock.EXPECT().Rollback(s.ctx).Return(nil),
s.mock.EXPECT().BeginTX(s.ctx).Return(nil, fmt.Errorf("failed to begin")),
s.mock.EXPECT().Commit(s.ctx).Return(fmt.Errorf("failed to commit")),
s.mock.EXPECT().Rollback(s.ctx).Return(fmt.Errorf("failed to rollback")),
)
x, err := s.store.BeginTX(s.ctx)
s.Equal(s.ctx, x)
s.NoError(err)
s.NoError(s.store.Commit(s.ctx))
s.NoError(s.store.Rollback(s.ctx))
x, err = s.store.BeginTX(s.ctx)
s.Equal(nil, x)
s.EqualError(err, "failed to begin")
s.EqualError(s.store.Commit(s.ctx), "failed to commit")
s.EqualError(s.store.Rollback(s.ctx), "failed to rollback")
}
func (s *StoreSuite) TestClientAssertionJWTValid() {
gomock.InOrder(
s.mock.
EXPECT().
LoadOAuth2BlacklistedJTI(s.ctx, "3a240379e8286a7a8ff5e99d68567e0e5e34e80168b8feffa89d3d33dea95b63").
Return(&model.OAuth2BlacklistedJTI{
ID: 1,
Signature: "3a240379e8286a7a8ff5e99d68567e0e5e34e80168b8feffa89d3d33dea95b63",
ExpiresAt: time.Now().Add(time.Hour),
}, nil),
s.mock.
EXPECT().
LoadOAuth2BlacklistedJTI(s.ctx, "e7f67ad76c80d57d34b19598462817932aec21d2806a08a786a8d4b9dd476068").
Return(&model.OAuth2BlacklistedJTI{
ID: 1,
Signature: "e7f67ad76c80d57d34b19598462817932aec21d2806a08a786a8d4b9dd476068",
ExpiresAt: time.Now().Add(-time.Hour),
}, nil),
s.mock.
EXPECT().
LoadOAuth2BlacklistedJTI(s.ctx, "f29ef0d85303a09411b76001c579980f1b1b7fc9deb1fa647875a724f4f231c6").
Return(nil, fmt.Errorf("failed to load")),
)
s.EqualError(s.store.ClientAssertionJWTValid(s.ctx, "066ee771-e156-4886-b99f-ee09b0d3edf4"), "jti_known")
s.NoError(s.store.ClientAssertionJWTValid(s.ctx, "5dad3ff7-e4f2-41b6-98a3-b73d872076ce"))
s.EqualError(s.store.ClientAssertionJWTValid(s.ctx, "65471ccb-d650-4006-a95f-cb4f4e3d7202"), "failed to load")
}
func (s *StoreSuite) TestCreateSessions() {
challenge := uuid.Must(uuid.NewRandom())
session := &model.OpenIDSession{
ChallengeID: challenge,
}
sessionData, _ := json.Marshal(session)
gomock.InOrder(
s.mock.
EXPECT().
SaveOAuth2Session(s.ctx, storage.OAuth2SessionTypeAuthorizeCode, model.OAuth2Session{ChallengeID: challenge, RequestID: "abc", ClientID: "example", Signature: "abc", Active: true, Session: sessionData}).
Return(nil),
s.mock.
EXPECT().
SaveOAuth2Session(s.ctx, storage.OAuth2SessionTypeAuthorizeCode, model.OAuth2Session{ChallengeID: challenge, RequestID: "abc", ClientID: "example", Signature: "abc", Active: true, Session: sessionData}).
Return(fmt.Errorf("duplicate key")),
s.mock.
EXPECT().
SaveOAuth2Session(s.ctx, storage.OAuth2SessionTypeAccessToken, model.OAuth2Session{ChallengeID: challenge, RequestID: "abc", ClientID: "example", Signature: "abc", Active: true, Session: sessionData}).
Return(nil),
s.mock.
EXPECT().
SaveOAuth2Session(s.ctx, storage.OAuth2SessionTypeRefreshToken, model.OAuth2Session{ChallengeID: challenge, RequestID: "abc", ClientID: "example", Signature: "abc", Active: true, Session: sessionData}).
Return(nil),
s.mock.
EXPECT().
SaveOAuth2Session(s.ctx, storage.OAuth2SessionTypeOpenIDConnect, model.OAuth2Session{ChallengeID: challenge, RequestID: "abc", ClientID: "example", Signature: "abc", Active: true, Session: sessionData}).
Return(nil),
s.mock.
EXPECT().
SaveOAuth2Session(s.ctx, storage.OAuth2SessionTypePKCEChallenge, model.OAuth2Session{ChallengeID: challenge, RequestID: "abc", ClientID: "example", Signature: "abc", Active: true, Session: sessionData}).
Return(nil),
s.mock.
EXPECT().
SaveOAuth2PARContext(s.ctx, model.OAuth2PARContext{Signature: "abc", RequestID: "abc", ClientID: "example", Session: sessionData}).
Return(nil),
)
s.NoError(s.store.CreateAuthorizeCodeSession(s.ctx, "abc", &fosite.Request{
ID: "abc",
Client: &oidc.BaseClient{
ID: "example",
},
Session: session,
}))
s.EqualError(s.store.CreateAuthorizeCodeSession(s.ctx, "abc", &fosite.Request{
ID: "abc",
Client: &oidc.BaseClient{
ID: "example",
},
Session: session,
}), "duplicate key")
s.EqualError(s.store.CreateAuthorizeCodeSession(s.ctx, "abc", &fosite.Request{
ID: "abc",
Client: &oidc.BaseClient{
ID: "example",
},
Session: nil,
}), "can't convert type '<nil>' to an *OAuth2Session")
s.NoError(s.store.CreateAccessTokenSession(s.ctx, "abc", &fosite.Request{
ID: "abc",
Client: &oidc.BaseClient{
ID: "example",
},
Session: session,
}))
s.NoError(s.store.CreateRefreshTokenSession(s.ctx, "abc", &fosite.Request{
ID: "abc",
Client: &oidc.BaseClient{
ID: "example",
},
Session: session,
}))
s.NoError(s.store.CreateOpenIDConnectSession(s.ctx, "abc", &fosite.Request{
ID: "abc",
Client: &oidc.BaseClient{
ID: "example",
},
Session: session,
}))
s.NoError(s.store.CreatePKCERequestSession(s.ctx, "abc", &fosite.Request{
ID: "abc",
Client: &oidc.BaseClient{
ID: "example",
},
Session: session,
}))
s.NoError(s.store.CreatePARSession(s.ctx, "abc", &fosite.AuthorizeRequest{
Request: fosite.Request{
ID: "abc",
Client: &oidc.BaseClient{
ID: "example",
},
Session: session,
}}))
s.EqualError(s.store.CreatePARSession(s.ctx, "abc", &fosite.AuthorizeRequest{
Request: fosite.Request{
ID: "abc",
Client: &oidc.BaseClient{
ID: "example",
},
Session: nil,
}}), "can't convert type '<nil>' to an *OAuth2Session")
}
func (s *StoreSuite) TestRevokeSessions() {
gomock.InOrder(
s.mock.
EXPECT().
DeactivateOAuth2Session(s.ctx, storage.OAuth2SessionTypeAuthorizeCode, "abc1").
Return(nil),
s.mock.
EXPECT().
DeactivateOAuth2Session(s.ctx, storage.OAuth2SessionTypeAuthorizeCode, "abc2").
Return(fmt.Errorf("not found")),
s.mock.
EXPECT().
RevokeOAuth2Session(s.ctx, storage.OAuth2SessionTypeAccessToken, "at_example1").
Return(nil),
s.mock.
EXPECT().
RevokeOAuth2Session(s.ctx, storage.OAuth2SessionTypeAccessToken, "at_example2").
Return(fmt.Errorf("not found")),
s.mock.
EXPECT().
RevokeOAuth2SessionByRequestID(s.ctx, storage.OAuth2SessionTypeAccessToken, "65471ccb-d650-4006-a95f-cb4f4e3d7200").
Return(nil),
s.mock.
EXPECT().
RevokeOAuth2SessionByRequestID(s.ctx, storage.OAuth2SessionTypeAccessToken, "65471ccb-d650-4006-a95f-cb4f4e3d7201").
Return(fmt.Errorf("not found")),
s.mock.
EXPECT().
RevokeOAuth2SessionByRequestID(s.ctx, storage.OAuth2SessionTypeAccessToken, "65471ccb-d650-4006-a95f-cb4f4e3d7202").
Return(sql.ErrNoRows),
s.mock.
EXPECT().
RevokeOAuth2Session(s.ctx, storage.OAuth2SessionTypeRefreshToken, "rt_example1").
Return(nil),
s.mock.
EXPECT().
RevokeOAuth2Session(s.ctx, storage.OAuth2SessionTypeRefreshToken, "rt_example2").
Return(fmt.Errorf("not found")),
s.mock.
EXPECT().
DeactivateOAuth2SessionByRequestID(s.ctx, storage.OAuth2SessionTypeRefreshToken, "65471ccb-d650-4006-a95f-cb4f4e3d7200").
Return(nil),
s.mock.
EXPECT().
DeactivateOAuth2SessionByRequestID(s.ctx, storage.OAuth2SessionTypeRefreshToken, "65471ccb-d650-4006-a95f-cb4f4e3d7201").
Return(fmt.Errorf("not found")),
s.mock.
EXPECT().
DeactivateOAuth2SessionByRequestID(s.ctx, storage.OAuth2SessionTypeRefreshToken, "65471ccb-d650-4006-a95f-cb4f4e3d7202").
Return(sql.ErrNoRows),
s.mock.
EXPECT().
DeactivateOAuth2SessionByRequestID(s.ctx, storage.OAuth2SessionTypeRefreshToken, "65471ccb-d650-4006-a95f-cb4f4e3d7200").
Return(nil),
s.mock.
EXPECT().
DeactivateOAuth2SessionByRequestID(s.ctx, storage.OAuth2SessionTypeRefreshToken, "65471ccb-d650-4006-a95f-cb4f4e3d7201").
Return(fmt.Errorf("not found")),
s.mock.
EXPECT().
DeactivateOAuth2SessionByRequestID(s.ctx, storage.OAuth2SessionTypeRefreshToken, "65471ccb-d650-4006-a95f-cb4f4e3d7202").
Return(sql.ErrNoRows),
s.mock.
EXPECT().
RevokeOAuth2Session(s.ctx, storage.OAuth2SessionTypePKCEChallenge, "pkce1").
Return(nil),
s.mock.
EXPECT().
RevokeOAuth2Session(s.ctx, storage.OAuth2SessionTypePKCEChallenge, "pkce2").
Return(fmt.Errorf("not found")),
s.mock.
EXPECT().
RevokeOAuth2Session(s.ctx, storage.OAuth2SessionTypeOpenIDConnect, "ac_1").
Return(nil),
s.mock.
EXPECT().
RevokeOAuth2Session(s.ctx, storage.OAuth2SessionTypeOpenIDConnect, "ac_2").
Return(fmt.Errorf("not found")),
s.mock.
EXPECT().
RevokeOAuth2PARContext(s.ctx, "urn:par1").
Return(nil),
s.mock.
EXPECT().
RevokeOAuth2PARContext(s.ctx, "urn:par2").
Return(fmt.Errorf("not found")),
)
s.NoError(s.store.InvalidateAuthorizeCodeSession(s.ctx, "abc1"))
s.EqualError(s.store.InvalidateAuthorizeCodeSession(s.ctx, "abc2"), "not found")
s.NoError(s.store.DeleteAccessTokenSession(s.ctx, "at_example1"))
s.EqualError(s.store.DeleteAccessTokenSession(s.ctx, "at_example2"), "not found")
s.NoError(s.store.RevokeAccessToken(s.ctx, "65471ccb-d650-4006-a95f-cb4f4e3d7200"))
s.EqualError(s.store.RevokeAccessToken(s.ctx, "65471ccb-d650-4006-a95f-cb4f4e3d7201"), "not found")
s.EqualError(s.store.RevokeAccessToken(s.ctx, "65471ccb-d650-4006-a95f-cb4f4e3d7202"), "not_found")
s.NoError(s.store.DeleteRefreshTokenSession(s.ctx, "rt_example1"))
s.EqualError(s.store.DeleteRefreshTokenSession(s.ctx, "rt_example2"), "not found")
s.NoError(s.store.RevokeRefreshToken(s.ctx, "65471ccb-d650-4006-a95f-cb4f4e3d7200"))
s.EqualError(s.store.RevokeRefreshToken(s.ctx, "65471ccb-d650-4006-a95f-cb4f4e3d7201"), "not found")
s.EqualError(s.store.RevokeRefreshToken(s.ctx, "65471ccb-d650-4006-a95f-cb4f4e3d7202"), "sql: no rows in result set")
s.NoError(s.store.RevokeRefreshTokenMaybeGracePeriod(s.ctx, "65471ccb-d650-4006-a95f-cb4f4e3d7200", "1"))
s.EqualError(s.store.RevokeRefreshTokenMaybeGracePeriod(s.ctx, "65471ccb-d650-4006-a95f-cb4f4e3d7201", "2"), "not found")
s.EqualError(s.store.RevokeRefreshTokenMaybeGracePeriod(s.ctx, "65471ccb-d650-4006-a95f-cb4f4e3d7202", "3"), "sql: no rows in result set")
s.NoError(s.store.DeletePKCERequestSession(s.ctx, "pkce1"))
s.EqualError(s.store.DeletePKCERequestSession(s.ctx, "pkce2"), "not found")
s.NoError(s.store.DeleteOpenIDConnectSession(s.ctx, "ac_1"))
s.EqualError(s.store.DeleteOpenIDConnectSession(s.ctx, "ac_2"), "not found")
s.NoError(s.store.DeletePARSession(s.ctx, "urn:par1"))
s.EqualError(s.store.DeletePARSession(s.ctx, "urn:par2"), "not found")
}
func (s *StoreSuite) TestGetSessions() {
challenge := uuid.Must(uuid.NewRandom())
session := &model.OpenIDSession{
ChallengeID: challenge,
ClientID: "hs256",
}
sessionData, _ := json.Marshal(session)
sessionb := &model.OpenIDSession{
ChallengeID: challenge,
ClientID: "hs256",
}
sessionDatab, _ := json.Marshal(sessionb)
gomock.InOrder(
s.mock.EXPECT().LoadOAuth2Session(s.ctx, storage.OAuth2SessionTypeAuthorizeCode, "ac_123").
Return(&model.OAuth2Session{ClientID: "hs256", Session: sessionData, Active: true}, nil),
s.mock.EXPECT().LoadOAuth2Session(s.ctx, storage.OAuth2SessionTypeAuthorizeCode, "ac_456").
Return(&model.OAuth2Session{ClientID: "hs256", Session: sessionData, Active: false}, nil),
s.mock.EXPECT().LoadOAuth2Session(s.ctx, storage.OAuth2SessionTypeAuthorizeCode, "ac_aaa").
Return(nil, sql.ErrNoRows),
s.mock.EXPECT().LoadOAuth2Session(s.ctx, storage.OAuth2SessionTypeAuthorizeCode, "ac_130").
Return(nil, fmt.Errorf("timeout")),
s.mock.EXPECT().LoadOAuth2Session(s.ctx, storage.OAuth2SessionTypeAuthorizeCode, "ac_badclient").
Return(&model.OAuth2Session{ClientID: "no-client", Session: sessionDatab, Active: true}, nil),
s.mock.EXPECT().LoadOAuth2Session(s.ctx, storage.OAuth2SessionTypeAccessToken, "at").
Return(&model.OAuth2Session{ClientID: "hs256", Session: sessionData, Active: true}, nil),
s.mock.EXPECT().LoadOAuth2Session(s.ctx, storage.OAuth2SessionTypeRefreshToken, "rt").
Return(&model.OAuth2Session{ClientID: "hs256", Session: sessionData, Active: true}, nil),
s.mock.EXPECT().LoadOAuth2Session(s.ctx, storage.OAuth2SessionTypePKCEChallenge, "pkce").
Return(&model.OAuth2Session{ClientID: "hs256", Session: sessionData, Active: true}, nil),
s.mock.EXPECT().LoadOAuth2Session(s.ctx, storage.OAuth2SessionTypeOpenIDConnect, "ot").
Return(&model.OAuth2Session{ClientID: "hs256", Session: sessionData, Active: true}, nil),
s.mock.
EXPECT().
LoadOAuth2PARContext(s.ctx, "urn:par").
Return(&model.OAuth2PARContext{Signature: "abc", RequestID: "abc", ClientID: "hs256", Session: sessionData}, nil),
s.mock.
EXPECT().
LoadOAuth2PARContext(s.ctx, "urn:par").
Return(nil, sql.ErrNoRows),
)
var (
r fosite.Requester
err error
)
r, err = s.store.GetAuthorizeCodeSession(s.ctx, "ac_123", &model.OpenIDSession{})
s.NotNil(r)
s.NoError(err)
r, err = s.store.GetAuthorizeCodeSession(s.ctx, "ac_456", &model.OpenIDSession{})
s.NotNil(r)
s.EqualError(err, "Authorization code has ben invalidated")
r, err = s.store.GetAuthorizeCodeSession(s.ctx, "ac_aaa", &model.OpenIDSession{})
s.Nil(r)
s.EqualError(err, "not_found")
r, err = s.store.GetAuthorizeCodeSession(s.ctx, "ac_130", &model.OpenIDSession{})
s.Nil(r)
s.EqualError(err, "timeout")
r, err = s.store.GetAuthorizeCodeSession(s.ctx, "ac_badclient", &model.OpenIDSession{})
s.Nil(r)
s.EqualError(err, "invalid_client")
r, err = s.store.GetAccessTokenSession(s.ctx, "at", &model.OpenIDSession{})
s.NotNil(r)
s.NoError(err)
r, err = s.store.GetRefreshTokenSession(s.ctx, "rt", &model.OpenIDSession{})
s.NotNil(r)
s.NoError(err)
r, err = s.store.GetPKCERequestSession(s.ctx, "pkce", &model.OpenIDSession{})
s.NotNil(r)
s.NoError(err)
r, err = s.store.GetOpenIDConnectSession(s.ctx, "ot", &fosite.Request{
ID: "abc",
Client: &oidc.BaseClient{
ID: "example",
},
Session: session,
})
s.NotNil(r)
s.NoError(err)
r, err = s.store.GetPARSession(s.ctx, "urn:par")
s.NotNil(r)
s.NoError(err)
r, err = s.store.GetPARSession(s.ctx, "urn:par")
s.Nil(r)
s.EqualError(err, "sql: no rows in result set")
}
func (s *StoreSuite) TestIsJWTUsed() {
gomock.InOrder(
s.mock.
EXPECT().
LoadOAuth2BlacklistedJTI(s.ctx, "3a240379e8286a7a8ff5e99d68567e0e5e34e80168b8feffa89d3d33dea95b63").
Return(&model.OAuth2BlacklistedJTI{
ID: 1,
Signature: "3a240379e8286a7a8ff5e99d68567e0e5e34e80168b8feffa89d3d33dea95b63",
ExpiresAt: time.Now().Add(time.Hour),
}, nil),
s.mock.
EXPECT().
LoadOAuth2BlacklistedJTI(s.ctx, "e7f67ad76c80d57d34b19598462817932aec21d2806a08a786a8d4b9dd476068").
Return(&model.OAuth2BlacklistedJTI{
ID: 1,
Signature: "e7f67ad76c80d57d34b19598462817932aec21d2806a08a786a8d4b9dd476068",
ExpiresAt: time.Now().Add(-time.Hour),
}, nil),
s.mock.
EXPECT().
LoadOAuth2BlacklistedJTI(s.ctx, "f29ef0d85303a09411b76001c579980f1b1b7fc9deb1fa647875a724f4f231c6").
Return(nil, fmt.Errorf("failed to load")),
)
used, err := s.store.IsJWTUsed(s.ctx, "066ee771-e156-4886-b99f-ee09b0d3edf4")
s.True(used)
s.EqualError(err, "jti_known")
used, err = s.store.IsJWTUsed(s.ctx, "5dad3ff7-e4f2-41b6-98a3-b73d872076ce")
s.False(used)
s.NoError(err)
used, err = s.store.IsJWTUsed(s.ctx, "65471ccb-d650-4006-a95f-cb4f4e3d7202")
s.True(used)
s.EqualError(err, "failed to load")
}
func (s *StoreSuite) TestMarkJWTUsedForTime() {
gomock.InOrder(
s.mock.EXPECT().
SaveOAuth2BlacklistedJTI(s.ctx, model.OAuth2BlacklistedJTI{Signature: "f29ef0d85303a09411b76001c579980f1b1b7fc9deb1fa647875a724f4f231c6", ExpiresAt: time.Unix(160000000, 0)}).
Return(nil),
s.mock.EXPECT().SaveOAuth2BlacklistedJTI(s.ctx, model.OAuth2BlacklistedJTI{Signature: "0dab0de97ed4e05da82763497448daf4f6b555c99218100e3ef5a81f36232940", ExpiresAt: time.Unix(160000000, 0)}).
Return(fmt.Errorf("already marked")),
)
s.NoError(s.store.MarkJWTUsedForTime(s.ctx, "65471ccb-d650-4006-a95f-cb4f4e3d7202", time.Unix(160000000, 0)))
s.EqualError(s.store.MarkJWTUsedForTime(s.ctx, "65471ccb-d650-4006-a95f-cb4f4e3d7201", time.Unix(160000000, 0)), "already marked")
}

View File

@ -515,18 +515,20 @@ type OAuth2MutualTLSClientAuthenticationDiscoveryOptions struct {
within mtls_endpoint_aliases that do not define endpoints to which an OAuth client makes a direct request have
no meaning and SHOULD be ignored.
*/
MutualTLSEndpointAliases struct {
AuthorizationEndpoint string `json:"authorization_endpoint,omitempty"`
TokenEndpoint string `json:"token_endpoint,omitempty"`
IntrospectionEndpoint string `json:"introspection_endpoint,omitempty"`
RevocationEndpoint string `json:"revocation_endpoint,omitempty"`
EndSessionEndpoint string `json:"end_session_endpoint,omitempty"`
UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"`
BackChannelAuthenticationEndpoint string `json:"backchannel_authentication_endpoint,omitempty"`
FederationRegistrationEndpoint string `json:"federation_registration_endpoint,omitempty"`
PushedAuthorizationRequestEndpoint string `json:"pushed_authorization_request_endpoint,omitempty"`
RegistrationEndpoint string `json:"registration_endpoint,omitempty"`
} `json:"mtls_endpoint_aliases"`
MutualTLSEndpointAliases OAuth2MutualTLSClientAuthenticationAliasesDiscoveryOptions `json:"mtls_endpoint_aliases"`
}
type OAuth2MutualTLSClientAuthenticationAliasesDiscoveryOptions struct {
AuthorizationEndpoint string `json:"authorization_endpoint,omitempty"`
TokenEndpoint string `json:"token_endpoint,omitempty"`
IntrospectionEndpoint string `json:"introspection_endpoint,omitempty"`
RevocationEndpoint string `json:"revocation_endpoint,omitempty"`
EndSessionEndpoint string `json:"end_session_endpoint,omitempty"`
UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"`
BackChannelAuthenticationEndpoint string `json:"backchannel_authentication_endpoint,omitempty"`
FederationRegistrationEndpoint string `json:"federation_registration_endpoint,omitempty"`
PushedAuthorizationRequestEndpoint string `json:"pushed_authorization_request_endpoint,omitempty"`
RegistrationEndpoint string `json:"registration_endpoint,omitempty"`
}
type OAuth2JWTSecuredAuthorizationRequestDiscoveryOptions struct {
@ -954,15 +956,3 @@ type OpenIDConnectContext interface {
IssuerURL() (issuerURL *url.URL, err error)
}
// MockOpenIDConnectContext is a minimal implementation of OpenIDConnectContext for the purpose of testing.
type MockOpenIDConnectContext struct {
context.Context
MockIssuerURL *url.URL
}
// IssuerURL returns the MockIssuerURL.
func (m *MockOpenIDConnectContext) IssuerURL() (issuerURL *url.URL, err error) {
return m.MockIssuerURL, nil
}

View File

@ -1,6 +1,7 @@
package oidc
package oidc_test
import (
"context"
"net/url"
"testing"
"time"
@ -11,10 +12,11 @@ import (
"github.com/stretchr/testify/require"
"github.com/authelia/authelia/v4/internal/model"
"github.com/authelia/authelia/v4/internal/oidc"
)
func TestNewSession(t *testing.T) {
session := NewSession()
session := oidc.NewSession()
require.NotNil(t, session)
@ -34,24 +36,24 @@ func TestNewSessionWithAuthorizeRequest(t *testing.T) {
formValues := url.Values{}
formValues.Set(ClaimNonce, "abc123xyzauthelia")
formValues.Set(oidc.ClaimNonce, "abc123xyzauthelia")
request := &fosite.AuthorizeRequest{
Request: fosite.Request{
ID: requestID.String(),
Form: formValues,
Client: &BaseClient{ID: "example"},
Client: &oidc.BaseClient{ID: "example"},
},
}
extra := map[string]any{
ClaimPreferredUsername: "john",
oidc.ClaimPreferredUsername: "john",
}
requested := time.Unix(1647332518, 0)
authAt := time.Unix(1647332500, 0)
issuer := examplecom
amr := []string{AMRPasswordBasedAuthentication}
amr := []string{oidc.AMRPasswordBasedAuthentication}
consent := &model.OAuth2ConsentSession{
ChallengeID: uuid.New(),
@ -59,7 +61,7 @@ func TestNewSessionWithAuthorizeRequest(t *testing.T) {
Subject: uuid.NullUUID{UUID: subject, Valid: true},
}
session := NewSessionWithAuthorizeRequest(MustParseRequestURI(issuer), "primary", "john", amr, extra, authAt, consent, request)
session := oidc.NewSessionWithAuthorizeRequest(MustParseRequestURI(issuer), "primary", "john", amr, extra, authAt, consent, request)
require.NotNil(t, session)
require.NotNil(t, session.Extra)
@ -80,19 +82,36 @@ func TestNewSessionWithAuthorizeRequest(t *testing.T) {
assert.Equal(t, authAt, session.Claims.AuthTime)
assert.Equal(t, requested, session.Claims.RequestedAt)
assert.Equal(t, issuer, session.Claims.Issuer)
assert.Equal(t, "john", session.Claims.Extra[ClaimPreferredUsername])
assert.Equal(t, "john", session.Claims.Extra[oidc.ClaimPreferredUsername])
assert.Equal(t, "primary", session.Headers.Get(JWTHeaderKeyIdentifier))
assert.Equal(t, "primary", session.Headers.Get(oidc.JWTHeaderKeyIdentifier))
consent = &model.OAuth2ConsentSession{
ChallengeID: uuid.New(),
RequestedAt: requested,
}
session = NewSessionWithAuthorizeRequest(MustParseRequestURI(issuer), "primary", "john", nil, nil, authAt, consent, request)
session = oidc.NewSessionWithAuthorizeRequest(MustParseRequestURI(issuer), "primary", "john", nil, nil, authAt, consent, request)
require.NotNil(t, session)
require.NotNil(t, session.Claims)
assert.NotNil(t, session.Claims.Extra)
assert.Nil(t, session.Claims.AuthenticationMethodsReferences)
}
// MockOpenIDConnectContext is a minimal implementation of OpenIDConnectContext for the purpose of testing.
type MockOpenIDConnectContext struct {
context.Context
MockIssuerURL *url.URL
IssuerURLFunc func() (issuerURL *url.URL, err error)
}
// IssuerURL returns the MockIssuerURL.
func (m *MockOpenIDConnectContext) IssuerURL() (issuerURL *url.URL, err error) {
if m.IssuerURLFunc != nil {
return m.IssuerURLFunc()
}
return m.MockIssuerURL, nil
}

View File

@ -0,0 +1,16 @@
package utils
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestBytesJoin(t *testing.T) {
a := []byte("a")
b := []byte("b")
assert.Equal(t, "ab", string(BytesJoin(a, b)))
assert.Equal(t, "a", string(BytesJoin(a)))
assert.Equal(t, "", string(BytesJoin()))
}

View File

@ -0,0 +1,48 @@
package utils
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestTestingClock(t *testing.T) {
c := &TestingClock{
now: time.Unix(0, 0),
}
assert.Equal(t, int64(0), c.Now().Unix())
c.now = time.Unix(20, 0)
assert.Equal(t, int64(20), c.Now().Unix())
assert.Equal(t, int64(20000000000), c.Now().UnixNano())
c.Set(time.Unix(16000000, 0))
assert.Equal(t, int64(16000000), c.Now().Unix())
before := c.Now()
<-c.After(time.Millisecond * 100)
assert.Equal(t, before, c.Now())
}
func TestRealClock(t *testing.T) {
c := &RealClock{}
assert.WithinDuration(t, time.Now(), c.Now(), time.Second)
before := c.Now()
<-c.After(time.Millisecond * 100)
after := c.Now()
assert.WithinDuration(t, before, after, time.Millisecond*120)
diff := after.Sub(before)
assert.GreaterOrEqual(t, diff, time.Millisecond*100)
}