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

feat(authentication): add getIdTokenResult method #786

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
5 changes: 5 additions & 0 deletions .changeset/popular-cobras-camp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@capacitor-firebase/authentication': minor
---

feat: add `getIdTokenResult()` method
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import io.capawesome.capacitorjs.plugins.firebase.authentication.FirebaseAuthenticationHelper.ProviderId;
import io.capawesome.capacitorjs.plugins.firebase.authentication.classes.ConfirmVerificationCodeOptions;
import io.capawesome.capacitorjs.plugins.firebase.authentication.classes.GetIdTokenResult;
import io.capawesome.capacitorjs.plugins.firebase.authentication.classes.GetIdTokenResultResult;
import io.capawesome.capacitorjs.plugins.firebase.authentication.classes.LinkWithPhoneNumberOptions;
import io.capawesome.capacitorjs.plugins.firebase.authentication.classes.PhoneVerificationCompletedEvent;
import io.capawesome.capacitorjs.plugins.firebase.authentication.classes.SignInOptions;
Expand Down Expand Up @@ -194,6 +195,27 @@ public void getIdToken(Boolean forceRefresh, @NonNull final NonEmptyResultCallba
);
}

public void getIdTokenResult(Boolean forceRefresh, @NonNull final NonEmptyResultCallback callback) {
FirebaseUser user = getCurrentUser();
if (user == null) {
callback.error(new Exception(ERROR_NO_USER_SIGNED_IN));
return;
}
Task<GetTokenResult> idTokenResultTask = user.getIdToken(forceRefresh);
idTokenResultTask.addOnCompleteListener(
task -> {
if (task.isSuccessful()) {
GetTokenResult tokenResult = task.getResult();
GetIdTokenResultResult result = new GetIdTokenResultResult(tokenResult);
callback.success(result);
} else {
Exception exception = task.getException();
callback.error(exception);
}
}
);
}

public void getPendingAuthResult(PluginCall call) {
oAuthProviderHandler.getPendingAuthResult(call);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,33 @@ public void error(Exception exception) {
}
}

@PluginMethod
public void getIdTokenResult(PluginCall call) {
try {
Boolean forceRefresh = call.getBoolean("forceRefresh", false);

NonEmptyResultCallback callback = new NonEmptyResultCallback() {
@Override
public void success(Result result) {
call.resolve(result.toJSObject());
}

@Override
public void error(Exception exception) {
Logger.error(TAG, exception.getMessage(), exception);
String code = FirebaseAuthenticationHelper.createErrorCode(exception);
call.reject(exception.getMessage(), code);
}
};

implementation.getIdTokenResult(forceRefresh, callback);
} catch (Exception exception) {
Logger.error(TAG, exception.getMessage(), exception);
String code = FirebaseAuthenticationHelper.createErrorCode(exception);
call.reject(exception.getMessage(), code);
}
}

@PluginMethod
public void getPendingAuthResult(PluginCall call) {
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package io.capawesome.capacitorjs.plugins.firebase.authentication.classes;

import androidx.annotation.Nullable;
import com.getcapacitor.JSObject;
import com.google.firebase.auth.GetTokenResult;
import io.capawesome.capacitorjs.plugins.firebase.authentication.interfaces.Result;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;

public class GetIdTokenResultResult implements Result {

@Nullable
private GetTokenResult tokenResult;

private SimpleDateFormat iso8601Format;

public GetIdTokenResultResult(@Nullable GetTokenResult tokenResult) {
this.tokenResult = tokenResult;
this.iso8601Format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
this.iso8601Format.setTimeZone(TimeZone.getTimeZone("UTC"));
}

public JSObject toJSObject() {
JSObject result = new JSObject();
if (tokenResult != null) {
result.put("authTime", iso8601Format.format(new Date(tokenResult.getAuthTimestamp() * 1000L)));
result.put("expirationTime", iso8601Format.format(new Date(tokenResult.getExpirationTimestamp() * 1000L)));
result.put("issuedAtTime", iso8601Format.format(new Date(tokenResult.getIssuedAtTimestamp() * 1000L)));
result.put("signInProvider", tokenResult.getSignInProvider());
result.put("signInSecondFactor", tokenResult.getSignInSecondFactor());

JSObject claims = new JSObject();
for (String key : tokenResult.getClaims().keySet()) {
claims.put(key, tokenResult.getClaims().get(key));
}
result.put("claims", claims);
}
return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import Foundation
import FirebaseAuth
import Capacitor

@objc class GetIdTokenResultResult: NSObject {
let tokenResult: AuthTokenResult?

init(tokenResult: AuthTokenResult?) {
self.tokenResult = tokenResult
}

func toJSObject() -> JSObject {
var result = JSObject()

if let tokenResult = tokenResult {
let dateFormatter = ISO8601DateFormatter()

result["authTime"] = dateFormatter.string(from: tokenResult.authDate)
result["expirationTime"] = dateFormatter.string(from: tokenResult.expirationDate)
result["issuedAtTime"] = dateFormatter.string(from: tokenResult.issuedAtDate)
result["signInProvider"] = tokenResult.signInProvider
result["signInSecondFactor"] = tokenResult.signInSecondFactor

var claims = JSObject()
for (key, value) in tokenResult.claims {
if let jsonValue = value as? JSValue {
claims[key] = jsonValue
}
}
result["claims"] = claims
}

return result
}
}
17 changes: 17 additions & 0 deletions packages/authentication/ios/Plugin/FirebaseAuthentication.swift
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,23 @@ public typealias AuthStateChangedObserver = () -> Void
})
}

@objc func getIdTokenResult(_ forceRefresh: Bool, completion: @escaping (GetIdTokenResultResult?, Error?) -> Void) {
guard let user = self.getCurrentUser() else {
let error = RuntimeError(self.plugin.errorNoUserSignedIn)
completion(nil, error)
return
}
user.getIDTokenResult(forcingRefresh: forceRefresh) { tokenResult, error in
if let error = error {
CAPLog.print("[", self.plugin.tag, "] ", error)
completion(nil, error)
return
}
let idTokenResult = GetIdTokenResultResult(tokenResult: tokenResult)
completion(idTokenResult, nil)
}
}

@objc func getTenantId() -> String? {
return Auth.auth().tenantID
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
CAP_PLUGIN_METHOD(fetchSignInMethodsForEmail, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(getCurrentUser, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(getIdToken, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(getIdTokenResult, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(getPendingAuthResult, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(getRedirectResult, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(getTenantId, CAPPluginReturnPromise);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,22 @@ public class FirebaseAuthenticationPlugin: CAPPlugin {
})
}

@objc func getIdTokenResult(_ call: CAPPluginCall) {
let forceRefresh = call.getBool("forceRefresh", false)

implementation?.getIdTokenResult(forceRefresh, completion: { result, error in
if let error = error {
CAPLog.print("[", self.tag, "] ", error)
let code = FirebaseAuthenticationHelper.createErrorCode(error: error)
call.reject(error.localizedDescription, code)
return
}
if let result = result {
call.resolve(result.toJSObject())
}
})
}

@objc func getPendingAuthResult(_ call: CAPPluginCall) {
call.reject("Not available on iOS.")
}
Expand Down
63 changes: 63 additions & 0 deletions packages/authentication/src/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,14 @@ export interface FirebaseAuthenticationPlugin {
* @since 0.1.0
*/
getIdToken(options?: GetIdTokenOptions): Promise<GetIdTokenResult>;
/**
* Returns a deserialized JSON Web Token (JWT) used to identify the user to a Firebase service.
*
* @since 6.4.0
*/
getIdTokenResult(
options?: GetIdTokenResultOptions,
): Promise<GetIdTokenResultResult>;
/**
* Returns the `SignInResult` from the redirect-based sign-in flow.
*
Expand Down Expand Up @@ -655,6 +663,18 @@ export interface GetIdTokenOptions {
forceRefresh: boolean;
}

/**
* @since 6.4.0
*/
export interface GetIdTokenResultOptions {
/**
* Force refresh regardless of token expiration.
*
* @since 6.4.0
*/
forceRefresh: boolean;
}

/**
* @since 0.1.0
*/
Expand All @@ -667,6 +687,49 @@ export interface GetIdTokenResult {
token: string;
}

export interface GetIdTokenResultResult {
/**
* The authentication time formatted as a UTC string.
*
* This is the time the user authenticated (signed in) and not the time the token was refreshed.
*
* @since 6.4.0
*/
authTime: string;
/**
* The ID token expiration time formatted as a UTC string.
*
* @since 6.4.0
*/
expirationTime: string;
/**
* The ID token issuance time formatted as a UTC string.
*
* @since 6.4.0
*/
issuedAtTime: string;
/**
* The sign-in provider through which the ID token was obtained.
*
* @since 6.4.0
*/
signInProvider: string | null;
/**
* The type of second factor associated with this session, provided the user was multi-factor
* authenticated (eg. phone, etc).
*
* @since 6.4.0
*/
signInSecondFactor: string | null;
/**
* The entire payload claims of the ID token including the standard reserved claims as well as
* the custom claims.
*
* @since 6.4.0
*/
claims: Record<string, unknown>;
}

/**
* @since 1.1.0
*/
Expand Down
13 changes: 13 additions & 0 deletions packages/authentication/src/web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ import type {
FetchSignInMethodsForEmailResult,
FirebaseAuthenticationPlugin,
GetCurrentUserResult,
GetIdTokenResultResult,
GetIdTokenOptions,
GetIdTokenResult,
GetTenantIdResult,
Expand Down Expand Up @@ -217,6 +218,18 @@ export class FirebaseAuthenticationWeb
return result;
}

public async getIdTokenResult(
options?: GetIdTokenOptions,
): Promise<GetIdTokenResultResult> {
const auth = getAuth();
if (!auth.currentUser) {
throw new Error(FirebaseAuthenticationWeb.ERROR_NO_USER_SIGNED_IN);
}
const result: GetIdTokenResultResult =
await auth.currentUser.getIdTokenResult(options?.forceRefresh);
return result;
}

public async getRedirectResult(): Promise<SignInResult> {
const auth = getAuth();
const userCredential = await getRedirectResult(auth);
Expand Down