This Java client connects with Privy.io, enabling simple user management and secure event handling. It also includes a Spring Boot starter for quick integration.
<properties>
<privy.version>0.7.0</privy.version>
</properties>
<dependencies>
<dependency>
<groupId>dev.caceresenzo.privy</groupId>
<artifactId>client</artifactId>
<version>${privy.version}</version>
</dependency>
</dependencies>
PrivyClient client = PrivyClient.builder()
.applicationId("a0b1c2d3e4f5g6h7i8j9k0l1m")
.applicationSecret("a0b1c2d3e4f5g6h7i8j9k0l1m2n3o4p5q6r7s8t9u0v1w2x3y4z5a6b7c8d9e0f1g2h3i4j5k6l7m8n9o0p1q2r3")
.build();
Stream<User> users = findAllUsers();
/* or get a list via */
List<User> users = findAllUsers().toList();
Stream<User> users = findAllUsers("john");
/* or get a list via */
List<User> users = findAllUsers("john").toList();
Optional<User> user = client.findUserById("did:privy:a0b1c2d3e4f5g6h7i8j9k0l1m");
/* the "did:privy:" prefix is optional */
Optional<User> user = client.findUserById("a0b1c2d3e4f5g6h7i8j9k0l1m");
Optional<User> user = client.findUserByEmail("[email protected]");
Optional<User> user = client.findUserByWallet("0xa0b1c2d3e4f5g6h7i8j9k0l1m2n3o4p5q6r7s8t9");
Optional<User> user = client.findUserByWallet("+1 (234) 567-8912");
Optional<User> user = client.findUserByTwitterUsername("johndoe");
Optional<User> user = client.findUserByTwitterSubject("1234567890");
Optional<User> user = client.findUserByDiscordUsername("johndoe#0");
boolean deleted = client.deleteUserById("0xa0b1c2d3e4f5g6h7i8j9k0l1m2n3o4p5q6r7s8t9");
User user = client.findUserById("a0b1c2d3e4f5g6h7i8j9k0l1m").orElseThrow();
/* access it via known type */
Optional<LinkedAccount.Email> email = user.getEmail();
Optional<LinkedAccount.Google> google = user.getGoogle();
Optional<LinkedAccount.Github> github = user.getGithub();
/* access it via class */
Optional<LinkedAccount.Email> email = user.getAccount(LinkedAccount.Email.class);
Optional<LinkedAccount.Google> google = user.getAccount(LinkedAccount.Google.class);
Optional<LinkedAccount.Github> github = user.getAccount(LinkedAccount.Github.class);
/* iterate over accounts */
for (LinkedAccount account : user.getLinkedAccounts()) {
System.out.println(account);
// switch (account)
// see "Testing the linked account type"
}
Testing the linked account type
switch (account) {
case LinkedAccount.Wallet wallet -> {
System.out.println("Wallet");
System.out.println(" with address: %s".formatted(wallet.getAddress()));
System.out.println(" with chain id: %s".formatted(wallet.getChainId()));
}
case LinkedAccount.Email email -> {
System.out.println("Email");
System.out.println(" with address: %s".formatted(email.getAddress()));
}
case LinkedAccount.Phone phone -> {
System.out.println("Phone");
System.out.println(" with number: %s".formatted(phone.getNumber()));
}
case LinkedAccount.Google google -> {
System.out.println("Google %s".formatted(google.getSubject()));
System.out.println(" with email: %s".formatted(google.getEmail()));
System.out.println(" with name: %s".formatted(google.getName()));
}
case LinkedAccount.Twitter twitter -> {
System.out.println("Twitter %s".formatted(twitter.getSubject()));
System.out.println(" with username: %s".formatted(twitter.getUsername()));
System.out.println(" with name: %s".formatted(twitter.getName()));
}
case LinkedAccount.Discord discord -> {
System.out.println("Discord %s".formatted(discord.getSubject()));
System.out.println(" with username: %s".formatted(discord.getUsername()));
System.out.println(" with email: %s".formatted(discord.getEmail()));
}
case LinkedAccount.Github github -> {
System.out.println("Github %s".formatted(github.getSubject()));
System.out.println(" with username: %s".formatted(github.getUsername()));
System.out.println(" with name: %s".formatted(github.getName()));
}
case LinkedAccount.Other other -> {
System.out.println("Unknown %s".formatted(other.getType()));
System.out.println(" with properties: %s".formatted(other.getProperties()));
}
}
PrivyWebhook webhook = PrivyWebhook.builder()
.signingKey("whsec_a0b1c2d3e4f5g6h7i8j9k0l1m2n3o4p5")
.build();
PrivyWebhook.Headers headers = new PrivyWebhook.Headers(
"msg_a0b1c2d3e4f5g6h7i8j9k0l1m2n",
"1234567890",
"v1,a0b1c2d3e4f5g6h7i8j9k0l1m2n3o4p5q6r7s8t9u0v1"
);
String body = """
{
"message": "Hello, World!",
"type": "privy.test"
}
""";
Event event = webhook.verify(
headers,
body
);
Testing the event type
switch (event) {
case Event.Test test -> {
System.out.println("Testing: %s".formatted(test.getMessage()));
}
case Event.UserCreated userCreated -> {
System.out.println("User Created: %s".formatted(userCreated.getUser().getId()));
}
case Event.UserAuthenticated userAuthenticated -> {
System.out.println("User Authenticated: %s".formatted(userAuthenticated.getUser().getId()));
System.out.println(" with account: %s".formatted(userAuthenticated.getAccount()));
}
case Event.UserLinkedAccount userLinkedAccount -> {
System.out.println("User Linked Account: %s".formatted(userLinkedAccount.getUser().getId()));
System.out.println(" with account: %s".formatted(userLinkedAccount.getAccount()));
}
case Event.UserUnlinkedAccount userUnlinkedAccount -> {
System.out.println("User Unlinked Account: %s".formatted(userUnlinkedAccount.getUser().getId()));
System.out.println(" with account: %s".formatted(userUnlinkedAccount.getAccount()));
}
case Event.UserUpdatedAccount userUpdatedAccount -> {
System.out.println("User Updated Account: %s".formatted(userUpdatedAccount.getUser().getId()));
System.out.println(" with account: %s".formatted(userUpdatedAccount.getAccount()));
}
case Event.UserTransferredAccount userTransferredAccount -> {
System.out.println("User Transferred Account: %s -> %s".formatted(userTransferredAccount.getFromUser().getId(), userTransferredAccount.getToUser().getId()));
System.out.println(" with account: %s".formatted(userTransferredAccount.getAccount()));
System.out.println(" and the old user was deleted? %s".formatted(userTransferredAccount.isDeleted()));
}
case Event.Other other -> {
System.out.println("Unknown event: %s".formatted(other.getType()));
System.out.println(" with properties: %s".formatted(other.getProperties()));
}
}
There is a Spring Boot auto-configuration available.
<dependencies>
<dependency>
<groupId>dev.caceresenzo.privy</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${privy.version}</version>
</dependency>
</dependencies>
Which is enabled when the Application ID is specified in the configuration:
privy:
application-id: a0b1c2d3e4f5g6h7i8j9k0l1m
application-secret: a0b1c2d3e4f5g6h7i8j9k0l1m2n3o4p5q6r7s8t9u0v1w2x3y4z5a6b7c8d9e0f1g2h3i4j5k6l7m8n9o0p1q2r3
Which is enabled when the Webhook Signing Key is specified in the configuration:
privy:
webhook-signing-key: whsec_a0b1c2d3e4f5g6h7i8j9k0l1m2n3o4p5
@RestController
@RequestMapping(path = "/privy/webhook", produces = MediaType.APPLICATION_JSON_VALUE)
@RequiredArgsConstructor
public class ProvyRestControllerV1 {
private final PrivyWebhook privyWebhook;
@PostMapping
@ResponseStatus(HttpStatus.ACCEPTED)
public void webhook(
@RequestHeader(PrivyWebhook.Headers.ID_NAME) String id,
@RequestHeader(PrivyWebhook.Headers.TIMESTAMP_NAME) String timestamp,
@RequestHeader(PrivyWebhook.Headers.SIGNATURE_NAME) String signature,
@RequestBody String body
) {
PrivyWebhook.Headers headers = new PrivyWebhook.Headers(id, timestamp, signature);
Event event = privyWebhook.verify(headers, body);
System.out.println(event);
// switch (event)
// see "Testing the event type"
}
}
In order for Privy authentication to work in the Spring OAuth 2.0 Resource Server, it must be configured as follows:
spring:
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: ${PRIVY_JWKS_ENDPOINT}
jws-algorithms:
- ES256
issuer-uri: privy.io
# Optional, but increases security
audiences:
- ${PRIVY_APPLICATION_ID}
@RestController
@RequestMapping(path = "/hello", produces = MediaType.APPLICATION_JSON_VALUE)
public class HelloRestController {
@GetMapping
@PreAuthorize("authenticated")
public String greet(
@AuthenticationPrincipal Jwt jwt
) {
String subject = jwt.getSubject();
return "Welcome %s!".formatted(subject);
}
}