Skip to content

Commit d09cc0a

Browse files
authored
Merge pull request #5 from helioauth/feature/app-id-authentication
Support authentication for signup flow
2 parents a720a51 + eba4461 commit d09cc0a

File tree

15 files changed

+299
-32
lines changed

15 files changed

+299
-32
lines changed

docs/openapi/components/schemas/SignInStartResponse.yaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,5 @@ properties:
1010
options:
1111
type: string
1212
description: >
13-
JSON string representing the options argument that should be passed to `navigator.credentials.get(options)` if
13+
JSON object representing the options argument that should be passed to `navigator.credentials.get(options)` if
1414
`accountExists` is `true` or to `navigator.credentials.create(options)` if `accountExists` is `false`.
15-
x-field-extra-annotation: '@com.fasterxml.jackson.annotation.JsonRawValue'

docs/openapi/components/schemas/SignUpStartResponse.yaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,4 @@ properties:
88
description: Unique identifier for the sign-up request.
99
options:
1010
type: string
11-
description: Options to pass to `navigator.credentials.create()`
12-
x-field-extra-annotation: '@com.fasterxml.jackson.annotation.JsonRawValue'
11+
description: Options to pass to `navigator.credentials.create()` serialized in a JSON string

docs/openapi/openapi.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,11 @@ components:
7777
type: apiKey
7878
name: X-Api-Key
7979
in: header
80+
app-id:
81+
type: apiKey
82+
name: X-App-Id
83+
in: header
84+
app-api-key:
85+
type: apiKey
86+
name: X-Api-Key
87+
in: header

docs/openapi/paths/v1_signin_finish.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,5 @@ post:
1717
application/json:
1818
schema:
1919
$ref: ../components/schemas/SignInFinishResponse.yaml
20+
security:
21+
- app-api-key: []

docs/openapi/paths/v1_signin_start.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,5 @@ post:
2121
application/json:
2222
schema:
2323
$ref: ../components/schemas/SignInStartResponse.yaml
24+
security:
25+
- app-id: []

docs/openapi/paths/v1_signup_finish.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,5 @@ post:
1919
application/json:
2020
schema:
2121
$ref: ../components/schemas/SignUpFinishResponse.yaml
22+
security:
23+
- app-api-key: []

docs/openapi/paths/v1_signup_start.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,5 @@ post:
2121
application/json:
2222
schema:
2323
$ref: ../components/schemas/SignUpStartResponse.yaml
24+
security:
25+
- app-id: []

src/main/java/com/helioauth/passkeys/api/auth/RequestHeaderAuthenticationProvider.java renamed to src/main/java/com/helioauth/passkeys/api/auth/AdminApiAuthenticationProvider.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
*/
3434
@Service
3535
@RequiredArgsConstructor
36-
public class RequestHeaderAuthenticationProvider implements AuthenticationProvider {
36+
public class AdminApiAuthenticationProvider implements AuthenticationProvider {
3737

3838
private final AdminConfigProperties adminConfigProperties;
3939

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.helioauth.passkeys.api.auth;
18+
19+
import com.helioauth.passkeys.api.domain.ClientApplication;
20+
import com.helioauth.passkeys.api.domain.ClientApplicationRepository;
21+
import lombok.RequiredArgsConstructor;
22+
import org.springframework.security.authentication.AuthenticationProvider;
23+
import org.springframework.security.authentication.BadCredentialsException;
24+
import org.springframework.security.core.Authentication;
25+
import org.springframework.security.core.AuthenticationException;
26+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
27+
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
28+
import org.springframework.stereotype.Service;
29+
30+
import java.util.List;
31+
32+
@Service
33+
@RequiredArgsConstructor
34+
public class ApplicationApiKeyAuthenticationProvider implements AuthenticationProvider {
35+
36+
private final ClientApplicationRepository clientApplicationRepository;
37+
38+
@Override
39+
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
40+
String apiKeyHeader = (String) authentication.getPrincipal();
41+
42+
if (apiKeyHeader == null || apiKeyHeader.isBlank()) {
43+
throw new BadCredentialsException("Application API key header is missing or empty");
44+
}
45+
46+
ClientApplication clientApp = clientApplicationRepository.findByApiKey(apiKeyHeader)
47+
.orElseThrow(() -> new BadCredentialsException("Invalid api key"));
48+
49+
PreAuthenticatedAuthenticationToken authenticatedToken = new PreAuthenticatedAuthenticationToken(
50+
clientApp.getId(),
51+
clientApp,
52+
List.of(new SimpleGrantedAuthority("ROLE_APPLICATION"))
53+
);
54+
authenticatedToken.setDetails(clientApp);
55+
56+
return authenticatedToken;
57+
}
58+
59+
@Override
60+
public boolean supports(Class<?> authentication) {
61+
return PreAuthenticatedAuthenticationToken.class.isAssignableFrom(authentication);
62+
}
63+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.helioauth.passkeys.api.auth;
18+
19+
import com.helioauth.passkeys.api.domain.ClientApplication;
20+
import com.helioauth.passkeys.api.domain.ClientApplicationRepository;
21+
import lombok.RequiredArgsConstructor;
22+
import org.springframework.security.authentication.AuthenticationProvider;
23+
import org.springframework.security.authentication.BadCredentialsException;
24+
import org.springframework.security.core.Authentication;
25+
import org.springframework.security.core.AuthenticationException;
26+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
27+
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
28+
import org.springframework.stereotype.Service;
29+
30+
import java.util.List;
31+
import java.util.UUID;
32+
33+
@Service
34+
@RequiredArgsConstructor
35+
public class ApplicationIdAuthenticationProvider implements AuthenticationProvider {
36+
37+
private final ClientApplicationRepository clientApplicationRepository;
38+
39+
@Override
40+
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
41+
String appIdHeader = (String) authentication.getPrincipal();
42+
43+
if (appIdHeader == null || appIdHeader.isBlank()) {
44+
throw new BadCredentialsException("Application ID header is missing or empty");
45+
}
46+
47+
try {
48+
UUID appId = UUID.fromString(appIdHeader);
49+
ClientApplication clientApp = clientApplicationRepository.findById(appId)
50+
.orElseThrow(() -> new BadCredentialsException("Invalid application ID"));
51+
52+
PreAuthenticatedAuthenticationToken authenticatedToken = new PreAuthenticatedAuthenticationToken(
53+
clientApp.getId(),
54+
clientApp,
55+
List.of(new SimpleGrantedAuthority("ROLE_FRONTEND_APPLICATION"))
56+
);
57+
authenticatedToken.setDetails(clientApp);
58+
59+
return authenticatedToken;
60+
} catch (IllegalArgumentException e) {
61+
throw new BadCredentialsException("Invalid application ID format");
62+
}
63+
}
64+
65+
@Override
66+
public boolean supports(Class<?> authentication) {
67+
return PreAuthenticatedAuthenticationToken.class.isAssignableFrom(authentication);
68+
}
69+
}

0 commit comments

Comments
 (0)