<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Oddbit</title>
    <description>The latest articles on DEV Community by Oddbit (@oddbit).</description>
    <link>https://dev.to/oddbit</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F1500%2F452f8379-3f3a-4b93-81a6-3edc65f0aa2c.png</url>
      <title>DEV Community: Oddbit</title>
      <link>https://dev.to/oddbit</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/oddbit"/>
    <language>en</language>
    <item>
      <title>How to Keep Your Custom Claims in Sync with Roles Stored in Firestore</title>
      <dc:creator>Dennis Alund</dc:creator>
      <pubDate>Thu, 25 Apr 2024 06:50:36 +0000</pubDate>
      <link>https://dev.to/oddbit/how-to-keep-your-custom-claims-in-sync-with-roles-stored-in-firestore-93l</link>
      <guid>https://dev.to/oddbit/how-to-keep-your-custom-claims-in-sync-with-roles-stored-in-firestore-93l</guid>
      <description>&lt;p&gt;A common &lt;a href="https://stackoverflow.com/a/54394317/7967164"&gt;question I often encounter&lt;/a&gt;, is how to maintain consistency between custom claims in Firebase Auth and role assignments stored in Firestore.&lt;/p&gt;

&lt;p&gt;It is common in applications to have role-based authentication, where the access to resources is determined by a given role and where there are admin users have the authority to assign or revoke roles.&lt;/p&gt;

&lt;p&gt;While Firestore provides an excellent backend to manage such information, it's crucial that this role data also be useful in authorization logic. In Firebase this is by best practice implemented in &lt;a href="https://firebase.google.com/docs/firestore/security/get-started"&gt;Firestore rules&lt;/a&gt; and &lt;a href="https://firebase.google.com/docs/storage/security/get-started"&gt;Storage rules&lt;/a&gt; to declare resource access for database and files.&lt;/p&gt;

&lt;p&gt;One of the ways to implement this is to only keep the data in Firestore, and another way to do it is to maintain the information in &lt;a href="https://firebase.google.com/docs/auth/admin/custom-claims"&gt;auth custom claims&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Both solutions has a few considerations to keep in mind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Considering Your Options
&lt;/h2&gt;

&lt;p&gt;As an illustration of the considerations, consider these security rules that are implementing each solution for two separate areas of the database and storage.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;firestore.rules&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;service cloud.firestore {
  match /databases/{database}/documents {

    // Alt A: Using roles stored in Firestore user documents to determine access
    match /collection-a/{document} {
      allow read: if 'admin' in getUserRoles();
    }

    // Alt B: Using auth claims (role as an array) to determine access
    match /collection-b/{document} {
      allow read: if request.auth != null &amp;amp;&amp;amp; 'admin' in request.auth.token.roles;
    }    

    // Function to get user roles from Firestore document
    function getUserRoles() {
      return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.roles;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;storage.rules&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;service firebase.storage {
  match /b/{bucket}/o {

    // Alt A: Using roles in user documents to determine access
    match /folder-a/{allPaths=**} {
      allow read: if 'admin' in getUserRoles();
    }

    // Alt B: Using auth claims to determine access
    match /folder-b/{allPaths=**} {
      allow read: if request.auth != null &amp;amp;&amp;amp; 'admin' in request.auth.token.roles;
    }

    // Function to get user role from Firestore document
    function getRoleFromFirestore() {
      return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Option A: Firestore Document Lookups
&lt;/h3&gt;

&lt;p&gt;If you choose to use Firestore document lookups for role-based access control, you're leveraging a straightforward method that works well with Firestore and Cloud Storage.&lt;/p&gt;

&lt;p&gt;The primary drawback of this approach is its applicability is limited to just Firestore and Cloud Storage; it doesn't extend to Firebase's Realtime Database or other services that might benefit from integrated role-based access control.&lt;/p&gt;

&lt;p&gt;It is also important to note that using Firestore documents to check authorization rules in both Firestore and Cloud Storage incurs additional document read costs each time an access check is performed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option B: Using Firebase Auth Custom Claims
&lt;/h3&gt;

&lt;p&gt;The alternative involves replicating role information in Firebase Auth custom claims. This method offers broader integration across various services, including the Realtime Database and external API integrations where authentication data might be accessed via OAuth.&lt;/p&gt;

&lt;p&gt;To implement this, a dedicated cloud function is essential for synchronizing role updates from Firestore documents to Firebase Auth custom claims. This function ensures that any changes in user roles within Firestore are promptly reflected in Firebase Auth.&lt;/p&gt;

&lt;h4&gt;
  
  
  Implementing the Cloud Function
&lt;/h4&gt;

&lt;p&gt;The cloud function required for this task should:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Trigger on updates to the user document specifically related to role changes.&lt;/li&gt;
&lt;li&gt;Update Firebase Auth custom claims to reflect these changes.&lt;/li&gt;
&lt;li&gt;Maintain any other existing custom claims in the user's auth object.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s a simple example of such a cloud function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;updateUserRoles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;functions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firestore&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;document&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/users/{userId}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;change&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;beforeData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;change&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;before&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;afterData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;change&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;after&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Check if roles have changed&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;beforeData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;afterData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roles&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;functions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Roles are unchanged. Do nothing.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newRoles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;afterData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roles&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Get the current auth user and merge the new roles into the claims&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newClaims&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customClaims&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;roles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;newRoles&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;setCustomUserClaims&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newClaims&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Both approaches offer distinct advantages depending on your application's specific needs. Whether you prioritize broader service integration or a more focused, cost-effective solution within Firestore and Cloud Storage, understanding these options will empower you to make informed decisions about implementing role-based access control in your Firebase environment.&lt;/p&gt;

</description>
      <category>firebase</category>
      <category>firestore</category>
      <category>firebaseauth</category>
    </item>
    <item>
      <title>Backing up Firebase project with Github Actions</title>
      <dc:creator>Dennis Alund</dc:creator>
      <pubDate>Wed, 29 Apr 2020 15:38:05 +0000</pubDate>
      <link>https://dev.to/oddbit/backing-up-firebase-project-with-github-actions-4454</link>
      <guid>https://dev.to/oddbit/backing-up-firebase-project-with-github-actions-4454</guid>
      <description>&lt;p&gt;Backups are really useless most of the time... until something goes wrong and you really need one. &lt;/p&gt;

&lt;p&gt;This article will help provide some easy to apply recipes to back up your Firebase project, in a single Github Action workflow.&lt;/p&gt;

&lt;p&gt;To implement this workflow you will be needing &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://firebase.google.com/docs/cli#cli-ci-systems"&gt;Firebase token&lt;/a&gt; for CI/CD deployment.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://support.google.com/a/answer/7378726?hl=en"&gt;Service account&lt;/a&gt; that can access your Firebase project and storage buckets.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We will be making a snapshot backup that is being replaced for each run and another accumulative one that is adding every new&lt;br&gt;
backup into an historical archive.&lt;/p&gt;

&lt;p&gt;Make sure that your GCP project has a "backups bucket". It should be listed in your Google Cloud Platform Console, but not in the Firebase console. Replace &lt;code&gt;example&lt;/code&gt; in the URL below to check and confirm.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://console.cloud.google.com/storage/browser?project=example"&gt;https://console.cloud.google.com/storage/browser?project=example&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If your project ID is &lt;code&gt;example&lt;/code&gt;, then you should have a bucket called &lt;code&gt;example-backups&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;The bucket is automatically created if you have already configured &lt;a href="https://firebase.google.com/docs/database/backups"&gt;Firebase Database automated backups&lt;/a&gt; before. You can configure to switch them on right away to get two-for-one: both having the bucket generated and benefits of automated backups.&lt;/p&gt;

&lt;p&gt;We will be using this bucket to store our own backups too.&lt;/p&gt;
&lt;h3&gt;
  
  
  /.github/workflows/backup-snapshot.yml
&lt;/h3&gt;

&lt;p&gt;The schedule for your snapshot backups is up to you on how frequent you find it necessary. In this example we're making a nightly snapshot of the project.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h3&gt;
  
  
  /.github/workflows/backup-archive.yml
&lt;/h3&gt;

&lt;p&gt;The schedule for your archive backups is also up to your own needs, depending on how frequent you find it necessary. In this example we're making a monthly backup archive of the project.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h3&gt;
  
  
  Finally
&lt;/h3&gt;

&lt;p&gt;Sleep well knowing that your project is backed up on a regular basis.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>githubactions</category>
      <category>firebase</category>
      <category>backup</category>
    </item>
    <item>
      <title>Publishing Dart Packages with Github Actions</title>
      <dc:creator>Dennis Alund</dc:creator>
      <pubDate>Sat, 09 Nov 2019 13:24:21 +0000</pubDate>
      <link>https://dev.to/oddbit/publishing-dart-packages-with-github-actions-4i13</link>
      <guid>https://dev.to/oddbit/publishing-dart-packages-with-github-actions-4i13</guid>
      <description>&lt;p&gt;&lt;em&gt;A tutorial that will get you started with automated publishing of Dart packages to the pub.dev package repository, in less than 3 minutes.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In a couple of minutes from now, you will have a Github Action workflow that does all the heavy lifting of publishing your dart package or &lt;br&gt;
Flutter plugin to &lt;a href="https://pub.dev/"&gt;pub.dev&lt;/a&gt; for you.&lt;/p&gt;

&lt;p&gt;Let’s get started.&lt;/p&gt;
&lt;h2&gt;
  
  
  Get your credentials
&lt;/h2&gt;

&lt;p&gt;First you need to get and setup your credentials for publishing the package. Make sure that you publish your first version manually from the command line so that your package gets listed and you become the owner of that listing.&lt;/p&gt;

&lt;p&gt;After that, find your &lt;code&gt;credentials.json&lt;/code&gt; file either in &lt;code&gt;~/.pub-cache&lt;/code&gt; or &lt;code&gt;~/&amp;lt;your flutter root&amp;gt;/.pub-cache/&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; ~/.pub-cache/credentials.json 
&lt;span class="o"&gt;{&lt;/span&gt;
 “accessToken”:”&amp;lt;access-token&amp;gt;”,
 ”refreshToken”:”&amp;lt;refresh-token&amp;gt;”,
 ”tokenEndpoint”:”https://accounts.google.com/o/oauth2/token&lt;span class="s2"&gt;",
 "&lt;/span&gt;scopes&lt;span class="s2"&gt;":["&lt;/span&gt;openid&lt;span class="s2"&gt;","&lt;/span&gt;https://www.googleapis.com/auth/userinfo.email&lt;span class="s2"&gt;"],
 "&lt;/span&gt;expiration&lt;span class="s2"&gt;":1570721159347
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Copy your access and refresh tokens and &lt;a href="https://help.github.com/en/articles/virtual-environments-for-github-actions#creating-and-using-secrets-encrypted-variables"&gt;create secret variables in your Github project&lt;/a&gt; and name them &lt;br&gt;
&lt;code&gt;OAUTH_ACCESS_TOKEN&lt;/code&gt; and &lt;code&gt;OAUTH_REFRESH_TOKEN&lt;/code&gt; respectively. They will match the names in the script below later.&lt;/p&gt;
&lt;h2&gt;
  
  
  Set up your Github Actions workflow
&lt;/h2&gt;

&lt;p&gt;You can either choose to setup your actions direcly from the Github website or just add &lt;br&gt;
&lt;a href="https://gist.github.com/DennisAlund/e82039c42da7a67beaf6a0ab1bfd991f"&gt;this YAML configuration file&lt;/a&gt; &lt;br&gt;
into your project and push the change to the Github repository.&lt;/p&gt;
&lt;h3&gt;
  
  
  /.github/workflows/dart.yml
&lt;/h3&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;h2&gt;
  
  
  Publishing new versions
&lt;/h2&gt;

&lt;p&gt;This script will now publish a new version of your package to pub.dev every time you merge to the master branch.&lt;br&gt;
It’s important to rember that each new version that you are publishing must have a new semantic version number in your pubspec.yaml and you should also declare a corresponding explanation of what you have updated in the &lt;code&gt;CHANGELOG.md&lt;/code&gt;. The deployment &lt;strong&gt;will complain&lt;/strong&gt; if you don’t.&lt;/p&gt;

</description>
      <category>githubactions</category>
      <category>flutterplugin</category>
      <category>dartpackage</category>
      <category>dart</category>
    </item>
  </channel>
</rss>
