-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #31 from speakeasy-sdks/feat/add-authenticateRequest
feat: implement authenticateRequest and verifyToken helper methods
- Loading branch information
Showing
16 changed files
with
1,341 additions
and
386 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1 @@ | ||
src/main/java/com/clerk/backend_api/helpers/JwtHelper.java | ||
src/test/java/com/clerk/backend_api/helpers/JwtHelperTest.java | ||
src/main/java/com/clerk/backend_api/helpers/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
212 changes: 0 additions & 212 deletions
212
src/main/java/com/clerk/backend_api/helpers/JwtHelper.java
This file was deleted.
Oops, something went wrong.
30 changes: 30 additions & 0 deletions
30
src/main/java/com/clerk/backend_api/helpers/jwks/AuthErrorReason.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package com.clerk.backend_api.helpers.jwks; | ||
|
||
/** | ||
* AuthErrorReason - The reason for request authentication failure. | ||
*/ | ||
public enum AuthErrorReason implements ErrorReason { | ||
|
||
SESSION_TOKEN_MISSING( | ||
"session-token-missing", | ||
"Could not retrieve session token. Please make sure that the __session cookie or the HTTP authorization header contain a Clerk-generated session JWT"), | ||
SECRET_KEY_MISSING( | ||
"secret-key-missing", | ||
"Missing Clerk Secret Key. Go to https://dashboard.clerk.com and get your key for your instance."); | ||
|
||
private final String id; | ||
private final String message; | ||
|
||
private AuthErrorReason(String id, String message) { | ||
this.id = id; | ||
this.message = message; | ||
} | ||
|
||
public String id() { | ||
return id; | ||
} | ||
|
||
public String message() { | ||
return message; | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
src/main/java/com/clerk/backend_api/helpers/jwks/AuthStatus.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package com.clerk.backend_api.helpers.jwks; | ||
|
||
/** | ||
* AuthStatus - The request authentication status. | ||
*/ | ||
public enum AuthStatus { | ||
SIGNED_IN("signed-in"), | ||
SIGNED_OUT("signed-out"); | ||
|
||
private final String value; | ||
|
||
private AuthStatus(String value) { | ||
this.value = value; | ||
} | ||
|
||
public String value() { | ||
return value; | ||
} | ||
} |
98 changes: 98 additions & 0 deletions
98
src/main/java/com/clerk/backend_api/helpers/jwks/AuthenticateRequest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
package com.clerk.backend_api.helpers.jwks; | ||
|
||
import java.net.HttpCookie; | ||
import java.net.http.HttpHeaders; | ||
import java.net.http.HttpRequest; | ||
import java.util.List; | ||
import java.util.Optional; | ||
import java.util.concurrent.TimeUnit; | ||
|
||
/** | ||
* AuthenticateRequest - Helper methods to authenticate requests. | ||
*/ | ||
public final class AuthenticateRequest { | ||
|
||
private static final String SESSION_COOKIE_NAME = "__session"; | ||
|
||
private AuthenticateRequest() { | ||
// prevent instantiation (this is a utility class) | ||
} | ||
|
||
/** | ||
* Checks if the HTTP request is authenticated. | ||
* | ||
* First the session token is retrieved from either the __session cookie | ||
* or the HTTP Authorization header. | ||
* Then the session token is verified: networklessly if the options.jwtKey | ||
* is provided, otherwise by fetching the JWKS from Clerk's Backend API. | ||
* | ||
* @param options The request authentication options | ||
* @return The request state. | ||
* | ||
* WARNING: authenticateRequest is applicable in the context of Backend | ||
* APIs only. | ||
*/ | ||
public static final RequestState authenticateRequest(HttpRequest request, AuthenticateRequestOptions options) { | ||
|
||
String sessionToken = getSessionToken(request); | ||
if (sessionToken == null) { | ||
return RequestState.signedOut(AuthErrorReason.SESSION_TOKEN_MISSING); | ||
} | ||
|
||
VerifyTokenOptions verifyTokenOptions; | ||
|
||
if (options.jwtKey().isPresent()) { | ||
verifyTokenOptions = VerifyTokenOptions // | ||
.jwtKey(options.jwtKey().get()) // | ||
.audience(options.audience()) // | ||
.authorizedParties(options.authorizedParties()) // | ||
.clockSkew(options.clockSkewInMs(), TimeUnit.MILLISECONDS) // | ||
.build(); | ||
} else if (options.secretKey().isPresent()) { | ||
verifyTokenOptions = VerifyTokenOptions // | ||
.secretKey(options.secretKey().get()) // | ||
.audience(options.audience()) // | ||
.authorizedParties(options.authorizedParties()) // | ||
.clockSkew(options.clockSkewInMs(), TimeUnit.MILLISECONDS) // | ||
.build(); | ||
} else { | ||
return RequestState.signedOut(AuthErrorReason.SECRET_KEY_MISSING); | ||
} | ||
|
||
try { | ||
VerifyToken.verifyToken(sessionToken, verifyTokenOptions); | ||
} catch (TokenVerificationException e) { | ||
return RequestState.signedOut(e.reason()); | ||
} | ||
|
||
return RequestState.signedIn(sessionToken); | ||
} | ||
|
||
/** | ||
* Retrieve token from __session cookie or Authorization header. | ||
* | ||
* @param request The HTTP request | ||
* @return The session token, if present | ||
*/ | ||
private static String getSessionToken(HttpRequest request) { | ||
HttpHeaders headers = request.headers(); | ||
|
||
Optional<String> bearerToken = headers.firstValue("Authorization"); | ||
if (bearerToken.isPresent()) { | ||
return bearerToken.get().replace("Bearer ", ""); | ||
} | ||
|
||
Optional<String> cookieHeader = headers.firstValue("cookie"); | ||
if (cookieHeader.isPresent()) { | ||
String cookieHeaderValue = cookieHeader.get(); | ||
List<HttpCookie> cookies = HttpCookie.parse(cookieHeaderValue); | ||
for (HttpCookie cookie : cookies) { | ||
if (SESSION_COOKIE_NAME.equals(cookie.getName())) { | ||
return cookie.getValue(); | ||
} | ||
} | ||
} | ||
|
||
return null; | ||
} | ||
} |
Oops, something went wrong.