Search code examples
firebasespring-bootherokugoogle-cloud-storagefirebase-storage

Add Firebase Storage service credentials in json file to Heroku config vars for a spring boot app


I have a spring boot app deployed on Heroku which is also using Firebase Storage to store files. Everything works fine locally as I am able to authenticate to Firebase Storage by specifying the path to the firebase admin sdk service account key like this:

      FileInputStream serviceAccount =
            new FileInputStream("path/to/key.json");
      StorageOptions.newBuilder()
            .setProjectId(projectId)
            .setCredentials(GoogleCredentials.fromStream(serviceAccount)).build();

it is not safe to add the service account key to the project which would then be committed to git. How can this be externalized such that the service key is part of Heroku's config vars when deployed to Heroku? I have tried adding the raw json content to the application.properties and reading to a temp file but I get an error when I try to set the credentials from the temp file path.

     Path tempFile = createTempFile();
    if (tempFile == null) throw new Exception("google storage credentials not found");
    FileInputStream serviceAccount =
            new FileInputStream(tempFile.toString);
    StorageOptions.newBuilder()
            .setProjectId(projectId)
            .setCredentials(GoogleCredentials.fromStream(serviceAccount)).build();


  //create temp file
  private Path createTempFile() {
    Path path = null;
    try {

        path = Files.createTempFile("serviceaccount", ".json");
        System.out.println("Temp file : " + path);
        //writing data
        String credentials = environment.getRequiredProperty("GOOGLE_APPLICATION_CREDENTIALS");
        byte[] buffer = credentials.getBytes();
        Files.write(path, buffer);
    } catch (IOException e) {
        e.printStackTrace();
    }
    return path;
}

Solution

  • I finally got an idea from discussions with a friend on a way to go about this. First, I had to create a class that contains fields to hold the contents of the json credentials. The class is as follows:

    public class FirebaseCredential {
    private String type;
    private String project_id;
    private String private_key_id;
    private String private_key;
    private String client_email;
    private String client_id;
    private String auth_uri;
    private String token_uri;
    private String auth_provider_x509_cert_url;
    private String client_x509_cert_url;
    
    
    public String getType() {
        return type;
    }
    
    public String getProject_id() {
        return project_id;
    }
    
    public String getPrivate_key_id() {
        return private_key_id;
    }
    
    public String getPrivate_key() {
        return private_key;
    }
    
    public String getClient_email() {
        return client_email;
    }
    
    public String getClient_id() {
        return client_id;
    }
    
    public String getAuth_uri() {
        return auth_uri;
    }
    
    public String getToken_uri() {
        return token_uri;
    }
    
    public String getAuth_provider_x509_cert_url() {
        return auth_provider_x509_cert_url;
    }
    
    public String getClient_x509_cert_url() {
        return client_x509_cert_url;
    }
    
    public void setType(String type) {
        this.type = type;
    }
    
    public void setProject_id(String project_id) {
        this.project_id = project_id;
    }
    
    public void setPrivate_key_id(String private_key_id) {
        this.private_key_id = private_key_id;
    }
    
    public void setPrivate_key(String private_key) {
        this.private_key = private_key;
    }
    
    public void setClient_email(String client_email) {
        this.client_email = client_email;
    }
    
    public void setClient_id(String client_id) {
        this.client_id = client_id;
    }
    
    public void setAuth_uri(String auth_uri) {
        this.auth_uri = auth_uri;
    }
    
    public void setToken_uri(String token_uri) {
        this.token_uri = token_uri;
    }
    
    public void setAuth_provider_x509_cert_url(String auth_provider_x509_cert_url) {
        this.auth_provider_x509_cert_url = auth_provider_x509_cert_url;
    }
    
    public void setClient_x509_cert_url(String client_x509_cert_url) {
        this.client_x509_cert_url = client_x509_cert_url;
    }}
    

    I then created the following environment properties to hold the values of the json credentials file:

    FIREBASE_BUCKET_NAME=<add-the-value-from-config.json>
    FIREBASE_PROJECT_ID=<add-the-value-from-config.json>
    FIREBASE_TYPE=<add-the-value-from-config.json>
    FIREBASE_PRIVATE_KEY_ID=<add-the-value-from-config.json>
    FIREBASE_PRIVATE_KEY=<add-the-value-from-config.json>
    FIREBASE_CLIENT_EMAIL=<add-the-value-from-config.json>
    FIREBASE_CLIENT_ID=<add-the-value-from-config.json>
    FIREBASE_AUTH_URI=<add-the-value-from-config.json>
    FIREBASE_TOKEN_URI=<add-the-value-from-config.json>
    FIREBASE_AUTH_PROVIDER_X509_CERT_URL=<add-the-value-from-config.json>
    FIREBASE_CLIENT_X509_CERT_URL=<add-the-value-from-config.json>
    

    With the properties set up, it is possible to read the environment values and set them in a FirebaseCredential object, serialize the object to a json string and finally convert it to an InputStream object as seen below:

     private InputStream createFirebaseCredential() throws Exception {
        //private key
        String privateKey = environment.getRequiredProperty("FIREBASE_PRIVATE_KEY").replace("\\n", "\n");
    
        FirebaseCredential firebaseCredential = new FirebaseCredential();
        firebaseCredential.setType(environment.getRequiredProperty("FIREBASE_TYPE"));
        firebaseCredential.setProject_id(projectId);
        firebaseCredential.setPrivate_key_id("FIREBASE_PRIVATE_KEY_ID");
        firebaseCredential.setPrivate_key(privateKey);
        firebaseCredential.setClient_email(environment.getRequiredProperty("FIREBASE_CLIENT_EMAIL"));
        firebaseCredential.setClient_id(environment.getRequiredProperty("FIREBASE_CLIENT_ID"));
        firebaseCredential.setAuth_uri(environment.getRequiredProperty("FIREBASE_AUTH_URI"));
        firebaseCredential.setToken_uri(environment.getRequiredProperty("FIREBASE_TOKEN_URI"));
        firebaseCredential.setAuth_provider_x509_cert_url(environment.getRequiredProperty("FIREBASE_AUTH_PROVIDER_X509_CERT_URL"));
        firebaseCredential.setClient_x509_cert_url(environment.getRequiredProperty("FIREBASE_CLIENT_X509_CERT_URL"));
        //serialization of the object to json string
        ObjectMapper mapper = new ObjectMapper();
        String jsonString = mapper.writeValueAsString(firebaseCredential);
    
        //convert jsonString string to InputStream using Apache Commons
        return IOUtils.toInputStream(jsonString);
    }
    

    The resulting InputStream object is used to initialize the Firebase Storage or Admin as the case may be.

    InputStream firebaseCredentialStream = createFirebaseCredential();
    StorageOptions.newBuilder()
                .setProjectId(projected)                 
           .setCredentials(GoogleCredentials.fromStream(firebaseCredentialStream))
           .build();