Skip to content

Repository files navigation

Privy Client for Java

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.

Installation

<properties>
<privy.version>0.10.1</privy.version>
</properties>
<dependencies>
<dependency>
<groupId>dev.caceresenzo.privy</groupId>
<artifactId>client</artifactId>
<version>${privy.version}</version>
</dependency>
</dependencies>

Client

Configuration

PrivyClientclient = PrivyClient.builder()
.applicationId("a0b1c2d3e4f5g6h7i8j9k0l1m")
.applicationSecret("a0b1c2d3e4f5g6h7i8j9k0l1m2n3o4p5q6r7s8t9u0v1w2x3y4z5a6b7c8d9e0f1g2h3i4j5k6l7m8n9o0p1q2r3")
.build();

Usage

Stream Users

Stream<User> users = client.findAllUsers();
/* or get a list via */List<User> users = client.findAllUsers().toList();

Stream Users by a Search Term

Stream<User> users = client.findAllUsers("john");
/* or get a list via */List<User> users = client.findAllUsers("john").toList();

Find a User by an ID

Optional<User> user = client.findUserById("did:privy:a0b1c2d3e4f5g6h7i8j9k0l1m");
/* the "did:privy:" prefix is optional */Optional<User> user = client.findUserById("a0b1c2d3e4f5g6h7i8j9k0l1m");

Find a User by an Email Address

Optional<User> user = client.findUserByEmail("john.doe@gmail.com");

Find a User by a Wallet Address

Optional<User> user = client.findUserByWallet("0xa0b1c2d3e4f5g6h7i8j9k0l1m2n3o4p5q6r7s8t9");

Find a User by a Phone Number

Optional<User> user = client.findUserByPhone("+1 (234) 567-8912");

Find a User by a Twitter Username

Optional<User> user = client.findUserByTwitterUsername("johndoe");

Find a User by a Twitter Subject

Optional<User> user = client.findUserByTwitterSubject("1234567890");

Find a User by a Discord Username

Optional<User> user = client.findUserByDiscordUsername("johndoe#0");

Find a User by a Github Username

Optional<User> user = client.findUserByGithubUsername("johndoe");

Find a User by a Custom Auth Id

Optional<User> user = client.findUserByCustomAuthId("123456");

Set Custom Metadata for a User

CustomMetadatametadata = newCustomMetadata();
metadata.putString("planet", "Earth");
metadata.putNumber("age", 42);
metadata.putBoolean("powerful", true);
StringuserId = "a0b1c2d3e4f5g6h7i8j9k0l1m";
UserupdatedUser = client.setCustomMetadata(userId, metadata);

Delete a User by an ID

booleandeleted = client.deleteUserById("0xa0b1c2d3e4f5g6h7i8j9k0l1m2n3o4p5q6r7s8t9");

Linked Accounts

Useruser = 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 (LinkedAccountaccount : user.getLinkedAccounts()) {
System.out.println(account);
// switch (account)// see "Testing the linked account type"
}
Testing the linked account type
switch (account) {
caseLinkedAccount.Walletwallet -> {
System.out.println("Wallet");
System.out.println(" with address: %s".formatted(wallet.getAddress()));
System.out.println(" with chain id: %s".formatted(wallet.getChainId()));
}
caseLinkedAccount.Emailemail -> {
System.out.println("Email");
System.out.println(" with address: %s".formatted(email.getAddress()));
}
caseLinkedAccount.Phonephone -> {
System.out.println("Phone");
System.out.println(" with number: %s".formatted(phone.getNumber()));
}
caseLinkedAccount.Googlegoogle -> {
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()));
}
caseLinkedAccount.Twittertwitter -> {
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()));
}
caseLinkedAccount.Discorddiscord -> {
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()));
}
caseLinkedAccount.Githubgithub -> {
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()));
}
caseLinkedAccount.LinkedInlinkedIn -> {
System.out.println("LinkedIn %s".formatted(linkedIn.getSubject()));
System.out.println(" with name: %s".formatted(linkedIn.getName()));
}
caseLinkedAccount.Passkeypasskey -> {
System.out.println("Passkey");
System.out.println(" with credentials id: %s".formatted(passkey.getCredentialId()));
}
caseLinkedAccount.Otherother -> {
System.out.println("Unknown %s".formatted(other.getType()));
System.out.println(" with properties: %s".formatted(other.getProperties()));
}
}

Get the Verification Key

importjava.security.PublicKey;
PublicKeyverificationKey = client.getVerificationKey();

Note

The verification key is cached by default.
This behaviour can be disabled via the .cacheVerificationKey(false) method when building the client.

Verity an Auth Token

importio.jsonwebtoken.Claims;
importio.jsonwebtoken.Jws;
Stringtoken = request.getCookie("privy-token");
Jws<Claims> jwt = client.verifyAuthToken(token);
Claimspayload = jwt.getPayload();
System.out.println("User ID: %s".formatted(payload.getSubject()));

Tip

We recommend keeping the verification key caching enabled (default behavior) if it is being used for authenticating requests.

Get a User from an ID Token

StringidToken = request.getCookie("privy-id-token");
Useruser = client.getUserFromIdToken(idToken);

Tip

We recommend keeping the verification key caching enabled (default behavior) if it is being used for authenticating requests.

Advanced Configuration

The client can be configured further to meet the demands of the application:

PrivyClientclient = PrivyClient.builder()
/* change the api url */
.apiUrl("https://auth.privy.io")
/* mandatory credentials */
.applicationId("a0b1c2d3e4f5g6h7i8j9k0l1m")
.applicationSecret("a0b1c2d3e4f5g6h7i8j9k0l1m2n3o4p5q6r7s8t9u0v1w2x3y4z5a6b7c8d9e0f1g2h3i4j5k6l7m8n9o0p1q2r3")
/* change the iterator page size */
.maxPageSize(100)
/* should the key obtained via `client.getVerificationKey()` be cached? */
.cacheVerificationKey(true)
/* configure the JWT parser, usually not recommended, but can be useful for testing purposes. */
.jwtParserCustomizer((builder) -> builder
.clockSkewSeconds(60)
.clock(newFixedClock(System.currentTimeMillis())) /* stop the world */
.unsecured() /* enable `alg: none` */
)
.build();

Note

All values except those for applicationId, applicationSecret and jwtParserCustomizer are the default values.

Webhook

Configuration

PrivyWebhookwebhook = PrivyWebhook.builder()
.signingKey("whsec_a0b1c2d3e4f5g6h7i8j9k0l1m2n3o4p5")
.build();

Usage

Verify an Event

PrivyWebhook.Headersheaders = newPrivyWebhook.Headers(
"msg_a0b1c2d3e4f5g6h7i8j9k0l1m2n",
"1234567890",
"v1,a0b1c2d3e4f5g6h7i8j9k0l1m2n3o4p5q6r7s8t9u0v1"
);
Stringbody = """	{ "message": "Hello, World!", "type": "privy.test"	}	""";
Eventevent = webhook.verify(
headers,
body
);
Testing the event type
switch (receivedEvent) {
caseEvent.Testevent -> {
System.out.println("Testing: %s".formatted(event.getMessage()));
}
caseEvent.UserCreatedevent -> {
System.out.println("User Created: %s".formatted(event.getUser().getId()));
}
caseEvent.UserAuthenticatedevent -> {
System.out.println("User Authenticated: %s".formatted(event.getUser().getId()));
System.out.println(" with account: %s".formatted(event.getAccount()));
}
caseEvent.UserLinkedAccountevent -> {
System.out.println("User Linked Account: %s".formatted(event.getUser().getId()));
System.out.println(" with account: %s".formatted(event.getAccount()));
}
caseEvent.UserUnlinkedAccountevent -> {
System.out.println("User Unlinked Account: %s".formatted(event.getUser().getId()));
System.out.println(" with account: %s".formatted(event.getAccount()));
}
caseEvent.UserUpdatedAccountevent -> {
System.out.println("User Updated Account: %s".formatted(event.getUser().getId()));
System.out.println(" with account: %s".formatted(event.getAccount()));
}
caseEvent.UserTransferredAccountevent -> {
System.out.println("User Transferred Account: %s -> %s".formatted(event.getFromUser().getId(), event.getToUser().getId()));
System.out.println(" with account: %s".formatted(event.getAccount()));
System.out.println(" and the old user was deleted? %s".formatted(event.isDeleted()));
}
caseEvent.UserWalletCreatedevent -> {
System.out.println("User Wallet Created: %s".formatted(event.getUserId()));
System.out.println(" with wallet address: %s".formatted(event.getWallet().getAddress()));
}
caseEvent.MultiFactorAuthenticationEnabledevent -> {
System.out.println("Multi Factor Authentication Enabled: %s".formatted(event.getUserId()));
System.out.println(" with method: %s".formatted(event.getMethod()));
}
caseEvent.MultiFactorAuthenticationDisabledevent -> {
System.out.println("Multi Factor Authentication Disabled: %s".formatted(event.getUserId()));
System.out.println(" with method: %s".formatted(event.getMethod()));
}
caseEvent.WalletArchivedevent -> {
System.out.println("Wallet Archived: %s".formatted(event.getWalletAddress()));
}
caseEvent.WalletRestoredevent -> {
System.out.println("Wallet Restored: %s".formatted(event.getWalletAddress()));
}
caseEvent.FundsDepositedevent -> {
System.out.println("Funds Deposited: %s".formatted(event.getAsset()));
System.out.println(" from: %s".formatted(event.getSender()));
System.out.println(" to: %s".formatted(event.getRecipient()));
System.out.println(" for a total amount of: %s".formatted(event.getAmount()));
}
caseEvent.FundsWithdrawnevent -> {
System.out.println("Funds Withdrawn: %s".formatted(event.getAsset()));
System.out.println(" from: %s".formatted(event.getSender()));
System.out.println(" to: %s".formatted(event.getRecipient()));
System.out.println(" for a total amount of: %s".formatted(event.getAmount()));
}
caseEvent.PrivateKeyExportedevent -> {
System.out.println("Private Key Exported: %s".formatted(event.getUserId()));
System.out.println(" with wallet address: %s".formatted(event.getWalletAddress()));
}
caseEvent.SeedPhraseExportedevent -> {
System.out.println("Seed Phrase Exported: %s".formatted(event.getUserId()));
System.out.println(" with wallet address: %s".formatted(event.getWalletAddress()));
}
caseEvent.WalletRecoverySetupevent -> {
System.out.println("Wallet Recovery Setup: %s".formatted(event.getUserId()));
System.out.println(" with wallet address: %s".formatted(event.getWalletAddress()));
System.out.println(" with method: %s".formatted(event.getMethod()));
}
caseEvent.WalletRecoveredevent -> {
System.out.println("Wallet Recovered: %s".formatted(event.getUserId()));
System.out.println(" with wallet address: %s".formatted(event.getWalletAddress()));
}
caseEvent.Otherevent -> {
System.out.println("Unknown event: %s".formatted(event.getType()));
System.out.println(" with properties: %s".formatted(event.getProperties()));
}
}

Spring Boot Starter

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>

Client

Which is enabled when the Application ID is specified in the configuration:

privy:
application-id: a0b1c2d3e4f5g6h7i8j9k0l1mapplication-secret: a0b1c2d3e4f5g6h7i8j9k0l1m2n3o4p5q6r7s8t9u0v1w2x3y4z5a6b7c8d9e0f1g2h3i4j5k6l7m8n9o0p1q2r3

Webhook

Which is enabled when the Webhook Signing Key is specified in the configuration:

privy:
webhook-signing-key: whsec_a0b1c2d3e4f5g6h7i8j9k0l1m2n3o4p5

Controller Example

@RestController@RequestMapping(path = "/privy/webhook", produces = MediaType.APPLICATION_JSON_VALUE)
@RequiredArgsConstructorpublicclassProvyRestControllerV1 {
privatefinalPrivyWebhookprivyWebhook;
@PostMapping@ResponseStatus(HttpStatus.ACCEPTED)
publicvoidwebhook(
@RequestHeader(PrivyWebhook.Headers.ID_NAME) Stringid,
@RequestHeader(PrivyWebhook.Headers.TIMESTAMP_NAME) Stringtimestamp,
@RequestHeader(PrivyWebhook.Headers.SIGNATURE_NAME) Stringsignature,
@RequestBodyStringbody
) {
PrivyWebhook.Headersheaders = newPrivyWebhook.Headers(id, timestamp, signature);
Eventevent = privyWebhook.verify(headers, body);
System.out.println(event);
// switch (event)// see "Testing the event type"
}
}

Spring OAuth 2.0 Resource Server

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:
- ES256issuer-uri: privy.io# Optional, but increases securityaudiences:
- ${PRIVY_APPLICATION_ID}

Controller Example

@RestController@RequestMapping(path = "/hello", produces = MediaType.APPLICATION_JSON_VALUE)
publicclassHelloRestController {
@GetMapping@PreAuthorize("authenticated")
publicStringgreet(
@AuthenticationPrincipalJwtjwt
) {
Stringsubject = jwt.getSubject();
return"Welcome %s!".formatted(subject);
}
}

About

A Java API Client for Privy.io

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages