Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add PKCE parameters to relevant OAuth methods #536

Merged
merged 5 commits into from
Mar 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions bigbone-rx/src/main/kotlin/social/bigbone/rx/RxOAuthMethods.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ class RxOAuthMethods(client: MastodonClient) {
* @param clientId The client ID, obtained during app registration.
* @param redirectUri Set a URI to redirect the user to. Must match one of the redirect_uris declared during app registration.
* @param scope List of requested OAuth scopes, separated by spaces. Must be a subset of scopes declared during app registration.
* @param state Arbitrary value to pass through to your server when the user authorizes or rejects the authorization request.
* @param codeChallenge The PKCE code challenge for the authorization request.
* @param codeChallengeMethod Must currently be `S256`, as this is the only code challenge method that is supported by Mastodon for PKCE.
* @param forceLogin Forces the user to re-login, which is necessary for authorizing with multiple accounts from the same instance.
* @param languageCode The ISO 639-1 two-letter language code to use while rendering the authorization form.
* @see <a href="https://docs.joinmastodon.org/methods/oauth/#authorize">Mastodon oauth API methods #authorize</a>
Expand All @@ -33,13 +36,19 @@ class RxOAuthMethods(client: MastodonClient) {
clientId: String,
redirectUri: String,
scope: Scope? = null,
state: String? = null,
codeChallenge: String? = null,
codeChallengeMethod: String? = null,
forceLogin: Boolean? = null,
languageCode: String? = null
): Single<String> = Single.fromCallable {
oAuthMethods.getOAuthUrl(
clientId,
redirectUri,
scope,
state,
codeChallenge,
codeChallengeMethod,
forceLogin,
languageCode
)
Expand All @@ -51,6 +60,8 @@ class RxOAuthMethods(client: MastodonClient) {
* @param clientSecret The client secret, obtained during app registration.
* @param redirectUri Set a URI to redirect the user to. Must match one of the redirect_uris declared during app registration.
* @param code A user authorization code, obtained via the URL received from getOAuthUrl()
* @param codeVerifier Required if PKCE is used during the authorization request. This is the code verifier which was used
* to create the `codeChallenge` using the `codeChallengeMethod` for the authorization request.
* @param scope Requested OAuth scopes. Must be equal to the scope requested from the user while obtaining [code].
* If not provided, defaults to read.
* @see <a href="https://docs.joinmastodon.org/methods/oauth/#token">Mastodon oauth API methods #token</a>
Expand All @@ -61,13 +72,15 @@ class RxOAuthMethods(client: MastodonClient) {
clientSecret: String,
redirectUri: String,
code: String,
codeVerifier: String? = null,
scope: Scope? = null
): Single<Token> = Single.fromCallable {
oAuthMethods.getUserAccessTokenWithAuthorizationCodeGrant(
clientId,
clientSecret,
redirectUri,
code,
codeVerifier,
scope
).execute()
}
Expand Down
27 changes: 19 additions & 8 deletions bigbone/src/main/kotlin/social/bigbone/api/method/OAuthMethods.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ class OAuthMethods(private val client: MastodonClient) {
* @param clientId The client ID, obtained during app registration.
* @param redirectUri Set a URI to redirect the user to. Must match one of the redirect_uris declared during app registration.
* @param scope List of requested OAuth scopes, separated by spaces. Must be a subset of scopes declared during app registration.
* @param state Arbitrary value to pass through to your server when the user authorizes or rejects the authorization request.
* @param codeChallenge The PKCE code challenge for the authorization request.
* @param codeChallengeMethod Must currently be `S256`, as this is the only code challenge method that is supported by Mastodon for PKCE.
* @param forceLogin Forces the user to re-login, which is necessary for authorizing with multiple accounts from the same instance.
* @param languageCode The ISO 639-1 two-letter language code to use while rendering the authorization form.
* @see <a href="https://docs.joinmastodon.org/methods/oauth/#authorize">Mastodon oauth API methods #authorize</a>
Expand All @@ -31,6 +34,9 @@ class OAuthMethods(private val client: MastodonClient) {
clientId: String,
redirectUri: String,
scope: Scope? = null,
state: String? = null,
codeChallenge: String? = null,
codeChallengeMethod: String? = null,
forceLogin: Boolean? = null,
languageCode: String? = null
): String {
Expand All @@ -39,9 +45,12 @@ class OAuthMethods(private val client: MastodonClient) {
append("client_id", clientId)
append("redirect_uri", redirectUri)
append("response_type", "code")
scope?.let { append("scope", scope.toString()) }
forceLogin?.let { append("force_login", forceLogin) }
languageCode?.let { append("lang", languageCode) }
scope?.let { append("scope", it.toString()) }
state?.let { append("state", it) }
codeChallenge?.let { append("code_challenge", it) }
codeChallengeMethod?.let { append("code_challenge_method", it) }
forceLogin?.let { append("force_login", it) }
languageCode?.let { append("lang", it) }
}

return MastodonClient
Expand All @@ -61,6 +70,8 @@ class OAuthMethods(private val client: MastodonClient) {
* @param clientSecret The client secret, obtained during app registration.
* @param redirectUri Set a URI to redirect the user to. Must match one of the redirect_uris declared during app registration.
* @param code A user authorization code, obtained via the URL received from getOAuthUrl()
* @param codeVerifier Required if PKCE is used during the authorization request. This is the code verifier which was used
* to create the `codeChallenge` using the `codeChallengeMethod` for the authorization request.
* @param scope Requested OAuth scopes. Must be equal to the scope requested from the user while obtaining [code].
* If not provided, defaults to read.
* @see <a href="https://docs.joinmastodon.org/methods/oauth/#token">Mastodon oauth API methods #token</a>
Expand All @@ -71,6 +82,7 @@ class OAuthMethods(private val client: MastodonClient) {
clientSecret: String,
redirectUri: String,
code: String,
codeVerifier: String? = null,
scope: Scope? = null
): MastodonRequest<Token> {
return client.getMastodonRequest(
Expand All @@ -82,7 +94,8 @@ class OAuthMethods(private val client: MastodonClient) {
append("redirect_uri", redirectUri)
append("code", code)
append("grant_type", GrantTypes.AUTHORIZATION_CODE.value)
scope?.let { append("scope", scope.toString()) }
codeVerifier?.let { append("code_verifier", it) }
scope?.let { append("scope", it.toString()) }
}
)
}
Expand Down Expand Up @@ -111,9 +124,7 @@ class OAuthMethods(private val client: MastodonClient) {
append("client_id", clientId)
append("client_secret", clientSecret)
append("redirect_uri", redirectUri)
scope?.let {
append("scope", it.toString())
}
scope?.let { append("scope", it.toString()) }
append("grant_type", GrantTypes.CLIENT_CREDENTIALS.value)
}
)
Expand Down Expand Up @@ -150,7 +161,7 @@ class OAuthMethods(private val client: MastodonClient) {
append("username", username)
append("password", password)
append("grant_type", GrantTypes.PASSWORD.value)
scope?.let { append("scope", scope.toString()) }
scope?.let { append("scope", it.toString()) }
}
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,21 @@ public static void main(final String[] args) throws BigBoneRequestException {

final MastodonClient client = new MastodonClient.Builder(instanceName).build();
final Scope fullScope = new Scope(Scope.READ.ALL, Scope.WRITE.ALL, Scope.PUSH.ALL);
final String url = client.oauth().getOAuthUrl(clientId, redirectUri, fullScope);
final String state = "example_state";

// example values generated via: https://example-app.com/pkce,
// should be generated according to: https://oauth.net/2/pkce/
final String codeVerifier = "4d165eefac426b3e61ef652b7b44d63aad113b0540ad25f5f10bc935";
final String codeChallenge = "LYcAAS1MRj9aDDd1cgPxku0YgFtJFH6_3_RGCWZGvOc";

final String url = client.oauth().getOAuthUrl(
clientId,
redirectUri,
fullScope,
state,
codeChallenge,
"S256" // currently no other method supported
);
System.out.println("Open authorization page and copy code:");
System.out.println(url);
System.out.println("Paste code:");
Expand All @@ -30,6 +44,7 @@ public static void main(final String[] args) throws BigBoneRequestException {
clientSecret,
redirectUri,
authCode,
codeVerifier,
fullScope);

System.out.println("Access Token:");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,34 @@ object OAuthGetAccessToken {
val redirectUri = args[3]
val client = MastodonClient.Builder(instanceName).build()
val fullScope = Scope(Scope.READ.ALL, Scope.WRITE.ALL, Scope.PUSH.ALL)
val url = client.oauth.getOAuthUrl(clientId, redirectUri, fullScope)
val state = "example_state"

// example values generated via: https://example-app.com/pkce,
// should be generated according to: https://oauth.net/2/pkce/
val codeVerifier = "4d165eefac426b3e61ef652b7b44d63aad113b0540ad25f5f10bc935"
val codeChallenge = "LYcAAS1MRj9aDDd1cgPxku0YgFtJFH6_3_RGCWZGvOc"

val url = client.oauth.getOAuthUrl(
clientId = clientId,
redirectUri = redirectUri,
scope = fullScope,
state = state,
codeChallenge = codeChallenge,
// currently no other method supported
codeChallengeMethod = "S256"
)
println("Open authorization page and copy code:")
println(url)
println("Paste code:")
var authCode: String
Scanner(System.`in`).use { s -> authCode = s.nextLine() }
val token = client.oauth.getUserAccessTokenWithAuthorizationCodeGrant(
clientId,
clientSecret,
redirectUri,
authCode,
fullScope
clientId = clientId,
clientSecret = clientSecret,
redirectUri = redirectUri,
code = authCode,
codeVerifier = codeVerifier,
scope = fullScope
)
println("Access Token:")
println(token.execute().accessToken)
Expand Down