Skip to content

Commit

Permalink
Merge pull request #31 from speakeasy-sdks/feat/add-authenticateRequest
Browse files Browse the repository at this point in the history
feat: implement authenticateRequest and verifyToken helper methods
  • Loading branch information
speakeasybot authored Oct 29, 2024
2 parents 7a73908 + ca0e29d commit c79787a
Show file tree
Hide file tree
Showing 16 changed files with 1,341 additions and 386 deletions.
3 changes: 1 addition & 2 deletions .genignore
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/
9 changes: 9 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,12 @@ dependencies {


apply from: 'build-extras.gradle'

test {
testLogging {
showStandardStreams true
showStackTraces true
showExceptions true
exceptionFormat "full"
}
}
212 changes: 0 additions & 212 deletions src/main/java/com/clerk/backend_api/helpers/JwtHelper.java

This file was deleted.

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 src/main/java/com/clerk/backend_api/helpers/jwks/AuthStatus.java
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;
}
}
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;
}
}
Loading

0 comments on commit c79787a

Please sign in to comment.