# Overview The authentication is implemented in `internal/auth/`. In `auth.go` an interface is defined that any authentication provider must fulfill. It also acts as a dispatcher to delegate the calls to the available authentication providers. Two authentication types are available: * JWT authentication for the REST API that does not create a session cookie * Session based authentication using a session cookie The most important routines in auth are: * `Login()` Handle POST request to login user and start a new session * `Auth()` Authenticate user and put User Object in context of the request The http router calls auth in the following cases: * `r.Handle("/login", authentication.Login( ... )).Methods(http.MethodPost)`: The POST request on the `/login` route will call the Login callback. * Any route in the secured subrouter will always call Auth(), on success it will call the next handler in the chain, on failure it will render the login template. ``` secured.Use(func(next http.Handler) http.Handler { return authentication.Auth( // On success; next, // On failure: func(rw http.ResponseWriter, r *http.Request, err error) { // Render login form }) }) ``` A JWT token can be used to initiate an authenticated user session. This can either happen by calling the login route with a token provided in a header or via a special cookie containing the JWT token. For API routes the access is authenticated on every request using the JWT token and no session is initiated. # Login The Login function (located in `auth.go`): * Extracts the user name and gets the user from the user database table. In case the user is not found the user object is set to nil. * Iterates over all authenticators and: - Calls its `CanLogin` function which checks if the authentication method is supported for this user. - Calls its `Login` function to authenticate the user. On success a valid user object is returned. - Creates a new session object, stores the user attributes in the session and saves the session. - If the user does not yet exist in the database try to add the user - Starts the `onSuccess` http handler ## Local authenticator This authenticator is applied if ``` return user != nil && user.AuthSource == AuthViaLocalPassword ``` Compares the password provided by the login form to the password hash stored in the user database table: ``` if e := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(r.FormValue("password"))); e != nil { log.Errorf("AUTH/LOCAL > Authentication for user %s failed!", user.Username) return nil, fmt.Errorf("Authentication failed") } ``` ## LDAP authenticator This authenticator is applied if ``` return user != nil && user.AuthSource == AuthViaLDAP ``` Gets the LDAP connection and tries a bind with the provided credentials: ``` if err := l.Bind(userDn, r.FormValue("password")); err != nil { log.Errorf("AUTH/LDAP > Authentication for user %s failed: %v", user.Username, err) return nil, fmt.Errorf("Authentication failed") } ``` ## JWT Session authenticator Login via JWT token will create a session without password. For login the `X-Auth-Token` header is not supported. This authenticator is applied if the Authorization header is present: ``` return r.Header.Get("Authorization") != "" ``` The Login function: * Parses the token * Check if the signing method is EdDSA or HS256 or HS512 * Check if claims are valid and extracts the claims * The following claims have to be present: - `sub`: The subject, in this case this is the username - `exp`: Expiration in Unix epoch time - `roles`: String array with roles of user * In case user is not yet set, which is usually the case: - Try to fetch user from database - In case user is not yet present add user to user database table with `AuthViaToken` AuthSource. * Return valid user object ## JWT Cookie Session authenticator Login via JWT cookie token will create a session without password. It is first checked if the required configuration keys are set: * `publicKeyCrossLogin` * `TrustedExternalIssuer` * `CookieName` This authenticator is applied if the configured cookie is present: ``` jwtCookie, err := r.Cookie(cookieName) if err == nil && jwtCookie.Value != "" { return true } ``` The Login function: * Extracts and parses the token * Checks if signing method is Ed25519/EdDSA * In case publicKeyCrossLogin is configured: - Check if `iss` issuer claim matched trusted issuer from configuration - Return public cross login key - Otherwise return standard public key * Check if claims are valid * Depending on the option `ForceJWTValidationViaDatabase ` the roles are extracted from JWT token or taken from user object fetched from database * Ask browser to delete the JWT cookie * Return valid user object # Auth The Auth function (located in `auth.go`): * Returns a new http handler function that is defined right away * This handler tries two methods to authenticate a user: - Via a JWT API token in `AuthViaJWT()` - Via a valid session in `AuthViaSession()` * If err is not nil and the user object is valid it puts the user object in the request context and starts the onSuccess http handler * Otherwise it calls the onFailure handler ## AuthViaJWT Implemented in JWTAuthenticator: * Extract token either from header `X-Auth-Token` or `Authorization` with Bearer prefix * Parse token and check if it is valid. The Parse routine will also check if the token is expired. * If the option `ForceJWTValidationViaDatabase` is set it will ensure the user object exists in the database and takes the roles from the database user * Otherwise the roles are extracted from the roles claim * Returns a valid user object with AuthType set to AuthToken ## AuthViaSession * Extracts session * Get values username, projects, and roles from session * Returns a valid user object with AuthType set to AuthSession