Skip to content

[Auth jass] Additional login option with API Token ID - #3815

Open
lsafelix75 wants to merge 5 commits into
openhab:mainfrom
lsafelix75:auth
Open

[Auth jass] Additional login option with API Token ID#3815
lsafelix75 wants to merge 5 commits into
openhab:mainfrom
lsafelix75:auth

Conversation

@lsafelix75

@lsafelix75 lsafelix75 commented Sep 28, 2023

Copy link
Copy Markdown

Refer to: #3813 This commit comprises a few changes:

  1. Login screen - password field can be emptied but username must include generated API token with format oh.xxx.yyyyyyy
  2. Some housekeeping to Jaas code to remove unnecessary codes.

Signed-off-by: Felix Lo lsafelix75@gmail.com

Refer to: openhab#3813
This  commit comprises a few changes:
1) Login screen - password field can be emptied but username must be
generated API token
2) Some housekeeping to Jaas code to remove unnecessary codes.

Signed-off-by: Felix Lo <lsafelix75@gmail.com>
@lsafelix75
lsafelix75 requested a review from a team as a code owner September 28, 2023 13:56
@lsafelix75

Copy link
Copy Markdown
Author

@davidgraeff pls take a look. thanks

@lsafelix75 lsafelix75 changed the title Additional login option with API Token ID [Auth jass] Additional login option with API Token ID Sep 30, 2023
@openhab-bot

Copy link
Copy Markdown
Collaborator

This pull request has been mentioned on openHAB Community. There might be relevant details there:

https://community.openhab.org/t/x-openhab-token-use-for-mainui-authentication/149548/4

@splatch

splatch commented Oct 1, 2023

Copy link
Copy Markdown
Contributor

David might not be active any more in community. You can try with @ghys, @J-N-K or @hmerk.

@lsafelix75

lsafelix75 commented Oct 1, 2023

Copy link
Copy Markdown
Author

@J-N-K appreciate if you can take a look. Tq

@davidgraeff

Copy link
Copy Markdown
Contributor

David might not be active any more in community

That's exactly the case.

Sorry for the noise.

@openhab-bot

Copy link
Copy Markdown
Collaborator

This pull request has been mentioned on openHAB Community. There might be relevant details there:

https://community.openhab.org/t/rest-sitemaps-returning-401-even-when-authenticated-with-implicit-user-role-turned-off/154074/3

@wborn
wborn requested a review from Copilot July 26, 2025 16:10

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds the ability to authenticate using API tokens as an alternative to username/password authentication in openHAB's JAAS authentication system. The implementation allows users to enter API tokens in the username field while leaving the password field empty.

  • Modifies login flow to support API token authentication when password field is empty
  • Implements new JAAS login modules specifically for API token handling
  • Updates HTML form to make password field optional

Reviewed Changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
AbstractAuthPageServlet.java Adds conditional authentication logic to handle API tokens when password is empty
authorize.html Removes required attribute from password field to support API token login
ManagedUserLoginModule.java Updates login module to properly set user principals with roles
JaasAuthenticationProvider.java Implements dual authentication paths for username/password and API token credentials
ApiTokenLoginModule.java New login module dedicated to API token authentication
ApiTokenLoginConfiguration.java Configuration class for API token login module

UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(username, password);
Authentication auth = authProvider.authenticate(credentials);
Authentication auth;
if (!password.isEmpty()) {

Copilot AI Jul 26, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The authentication logic relies solely on an empty password to determine API token authentication. This could allow unintended authentication attempts if a user accidentally submits with an empty password. Consider adding explicit validation for API token format (e.g., checking for 'oh.' prefix) to ensure the username field actually contains a valid API token.

Copilot uses AI. Check for mistakes.
new ApiTokenLoginConfiguration());
loginContext.login();

return getAuthentication("", loginContext.getSubject());

Copilot AI Jul 26, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Passing an empty string as the username parameter to getAuthentication() for API token authentication could lead to incorrect authentication results. The method expects a valid username, and while it handles blank usernames by extracting from principals, this creates unnecessary complexity and potential for errors.

Suggested change
return getAuthentication("", loginContext.getSubject());
String username = extractUsernameFromSubject(loginContext.getSubject());
return getAuthentication(username, loginContext.getSubject());

Copilot uses AI. Check for mistakes.
Comment on lines +127 to +129
GenericUser user = (GenericUser) principals.iterator().next();
String[] roles = new String[user.getRoles().size()];

Copilot AI Jul 26, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code performs an unsafe cast without checking if the principal is actually a GenericUser instance. If the principals set contains other types of Principal objects, this will throw a ClassCastException at runtime.

Suggested change
GenericUser user = (GenericUser) principals.iterator().next();
String[] roles = new String[user.getRoles().size()];
GenericUser user = null;
for (Principal principal : principals) {
if (principal instanceof GenericUser) {
user = (GenericUser) principal;
break;
}
}
if (user == null) {
throw new CredentialException("No GenericUser found in principals");
}
String[] roles = new String[user.getRoles().size()];

Copilot uses AI. Check for mistakes.
Comment on lines +49 to +55
private CallbackHandler callbackHandler;

@Override
public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState,
Map<String, ?> options) {
this.subject = subject;
this.callbackHandler = callbackHandler;

Copilot AI Jul 26, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The callbackHandler field is assigned in the initialize method but never used throughout the class. This unused field should be removed to improve code clarity.

Suggested change
private CallbackHandler callbackHandler;
@Override
public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState,
Map<String, ?> options) {
this.subject = subject;
this.callbackHandler = callbackHandler;
@Override
public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState,
Map<String, ?> options) {
this.subject = subject;

Copilot uses AI. Check for mistakes.

@Override
public boolean abort() throws LoginException {
return false;

Copilot AI Jul 26, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The abort() method should clean up any state that was set during the login process. Since this module adds principals to the subject during login(), the abort() method should remove them if login was not committed.

Suggested change
return false;
if (this.subject != null) {
this.subject.getPrincipals().removeIf(principal -> principal instanceof GenericUser);
}
return true;

Copilot uses AI. Check for mistakes.

@Override
public boolean logout() throws LoginException {
return false;

Copilot AI Jul 26, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logout() method should remove the principals that were added during login from the subject. Currently returning false means the logout was not successful, but no cleanup is performed.

Suggested change
return false;
try {
// Remove all principals added by this LoginModule
this.subject.getPrincipals().removeIf(principal -> principal instanceof GenericUser);
return true;
} catch (Exception e) {
logger.error("Error during logout", e);
throw new LoginException("Logout failed: " + e.getMessage());
}

Copilot uses AI. Check for mistakes.
@wborn

wborn commented Aug 6, 2026

Copy link
Copy Markdown
Member

Hi @lsafelix75, Do you use jaas and how? If you use it we probably do not want to remove support for it (#5753). Perhaps you can also update this PR if you want to continue with it.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants