<?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: Yusuf kızılkan</title>
    <description>The latest articles on DEV Community by Yusuf kızılkan (@yusuf_kzlkan_bec8219b13).</description>
    <link>https://dev.to/yusuf_kzlkan_bec8219b13</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4052108%2F28b78e3d-fb00-4a0e-82ce-2d5a77d46bed.png</url>
      <title>DEV Community: Yusuf kızılkan</title>
      <link>https://dev.to/yusuf_kzlkan_bec8219b13</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yusuf_kzlkan_bec8219b13"/>
    <language>en</language>
    <item>
      <title>Everything your Flutter app needs after login (and why most starters stop there)</title>
      <dc:creator>Yusuf kızılkan</dc:creator>
      <pubDate>Thu, 06 Aug 2026 00:40:01 +0000</pubDate>
      <link>https://dev.to/yusuf_kzlkan_bec8219b13/everything-your-flutter-app-needs-after-login-and-why-most-starters-stop-there-12hf</link>
      <guid>https://dev.to/yusuf_kzlkan_bec8219b13/everything-your-flutter-app-needs-after-login-and-why-most-starters-stop-there-12hf</guid>
      <description>&lt;p&gt;Every Flutter starter kit solves the login screen. Almost none of them solve what comes after it.&lt;/p&gt;

&lt;p&gt;I spent the last few weeks building the "after" part for a real SaaS app, and it turned out to be six distinct problems — each one with a gotcha I didn't see coming. Here's what I learned, with the details that cost me the most time.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Token refresh is a queueing problem, not a retry problem&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The naive version: catch a 401, call /auth/refresh, retry the request. This works right up until your app fires three requests at once on a cold start. All three get a 401, all three call refresh, and now you have a race — two of them refresh with a token that's already been rotated server-side, and your user gets logged out for no reason.&lt;/p&gt;

&lt;p&gt;The fix is to treat refresh as a single-flight operation. When a 401 arrives, check whether a refresh is already in progress. If it is, queue the request and wait for the result instead of starting a second refresh:&lt;/p&gt;

&lt;p&gt;dart&lt;br&gt;
Future _refreshOnce() {&lt;br&gt;
  // If a refresh is already running, everyone waits on the same future.&lt;br&gt;
  return _refreshFuture ??= _doRefresh().whenComplete(() {&lt;br&gt;
    _refreshFuture = null;&lt;br&gt;
  });&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Small change, and it removes a whole class of "randomly logged out" bug reports.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your subscription state lives in two places, and they drift&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you use RevenueCat (or any IAP layer), the client knows whether the user is entitled. Your backend doesn't — until you tell it. And you need the backend to know, because the client can lie and because your server-side features need to gate on something.&lt;/p&gt;

&lt;p&gt;The answer is the webhook. RevenueCat posts events to your API, you map the entitlement to a column, done:&lt;/p&gt;

&lt;p&gt;python&lt;br&gt;
&lt;a class="mentioned-user" href="https://dev.to/router"&gt;@router&lt;/a&gt;.post("/webhooks/revenuecat")&lt;br&gt;
async def revenuecat_webhook(request: Request, db: Session = Depends(get_db)):&lt;br&gt;
    verify_signature(request)          # don't skip this&lt;br&gt;
    event = (await request.json())["event"]&lt;br&gt;
    user = db.query(User).filter(User.id == event["app_user_id"]).first()&lt;br&gt;
    user.subscription_status = (&lt;br&gt;
        "premium" if event["type"] in ACTIVE_EVENTS else "free"&lt;br&gt;
    )&lt;br&gt;
    db.commit()&lt;/p&gt;

&lt;p&gt;Two things people get wrong here: not verifying the signature (your endpoint is public, anyone can POST to it), and treating the webhook as the only source of truth. Webhooks get delayed and dropped. Read entitlements on the client too, and let the backend column be the authoritative one for server-side checks.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Push notifications need a device token table, not a user column&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It's tempting to store fcm_token on the user row. Then your user installs the app on a tablet, and the phone stops getting notifications.&lt;/p&gt;

&lt;p&gt;One user, many devices. Separate table, and handle token rotation — FCM rotates tokens on reinstall, restore, and sometimes for no visible reason:&lt;/p&gt;

&lt;p&gt;python&lt;br&gt;
class DeviceToken(Base):&lt;br&gt;
    &lt;strong&gt;tablename&lt;/strong&gt; = "device_tokens"&lt;br&gt;
    id = Column(Integer, primary_key=True)&lt;br&gt;
    user_id = Column(Integer, ForeignKey("users.id"))&lt;br&gt;
    token = Column(String, unique=True, index=True)&lt;/p&gt;

&lt;p&gt;Register on login, update on refresh, delete on logout.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Account deletion is an app store requirement with a trap in it&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both stores now require in-app account deletion. What's less obvious: deleting the account does not cancel the subscription. The user deletes their account, keeps getting charged, and blames you.&lt;/p&gt;

&lt;p&gt;So the flow has to be: check whether the user has an active subscription, and if they do, tell them plainly that they need to cancel through the store as well. Then soft-delete rather than hard-delete — you want the row for billing reconciliation, but you want the personal data gone:&lt;/p&gt;

&lt;p&gt;python&lt;br&gt;
user.deleted_at = datetime.utcnow()&lt;br&gt;
user.is_active = False&lt;br&gt;
user.email = f"deleted-{user.id}@example.invalid"&lt;br&gt;
user.full_name = None&lt;br&gt;
user.avatar_url = None&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your starter should run before any API keys exist&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This one is a design decision rather than a bug, and it changed how the whole project felt to work on.&lt;/p&gt;

&lt;p&gt;If your app crashes without google-services.json, then nobody — including future you — can clone it and see it run. So every external integration got a mock path: no Firebase credentials means push logs a warning and returns a mock response instead of throwing. No RevenueCat key means the paywall renders with fake products.&lt;/p&gt;

&lt;p&gt;python&lt;br&gt;
if not credentials_path and not credentials_json:&lt;br&gt;
    logger.warning("Firebase credentials not set — push running in mock mode")&lt;br&gt;
    return {"mode": "mock", "recipients": 0}&lt;/p&gt;

&lt;p&gt;The result: docker compose up, flutter run, and you have a working app in two minutes. You wire the real services when you actually need them.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You will want an admin panel sooner than you think&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first time a user emails "I can't log in", you'll be writing SQL by hand. If you're on FastAPI, sqladmin gives you a usable panel in an afternoon — user list, search, ban/activate — and you can secure it with the JWT auth you already built instead of inventing a second login system.&lt;/p&gt;

&lt;p&gt;Worth adding from day one: a filter for soft-deleted users (otherwise they clutter every list) and a stats page. Not because the numbers are sophisticated, but because "how many people signed up yesterday" is the question you'll ask most often.&lt;/p&gt;

&lt;p&gt;The Windows gotcha that ate an evening&lt;/p&gt;

&lt;p&gt;Unrelated to any of the above, but it cost me hours and almost nothing is written about it: if your Windows user folder is localized — mine is Masaüstü — Gradle and Flutter's shader compiler fail on the non-ASCII character. The error message points nowhere near the actual cause.&lt;/p&gt;

&lt;p&gt;Workaround: subst a drive letter to your project path, or move the project somewhere ASCII-only. Also, recent flutter_secure_storage versions need minSdk 23, which produces its own confusing build failure.&lt;/p&gt;

&lt;p&gt;If you'd rather not build all of this&lt;/p&gt;

&lt;p&gt;I packaged everything above into a starter kit — Flutter app plus FastAPI/PostgreSQL backend, with the auth, subscriptions, push, profile management, i18n and admin panel already wired together and tested end to end.&lt;/p&gt;

&lt;p&gt;Free auth module (MIT, just the login layer): github.com/yusufkizilkan/flutter-fastapi-auth-starter&lt;br&gt;
Full kit: kizilkan2.gumroad.com/l/flutter-saas-kit&lt;/p&gt;

&lt;p&gt;But honestly, the six problems above are worth understanding whether or not you use someone else's code for them. Most of my time went into the parts nobody writes tutorials about.&lt;/p&gt;

&lt;p&gt;What did I miss? If you've shipped a Flutter SaaS, I'd like to hear which of these bit you hardest.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>fastapi</category>
      <category>mobile</category>
      <category>saas</category>
    </item>
    <item>
      <title>I built a free full-stack auth starter so you can skip "auth week" (Flutter + FastAPI)</title>
      <dc:creator>Yusuf kızılkan</dc:creator>
      <pubDate>Tue, 28 Jul 2026 23:22:55 +0000</pubDate>
      <link>https://dev.to/yusuf_kzlkan_bec8219b13/i-built-a-free-full-stack-auth-starter-so-you-can-skip-auth-week-flutter-fastapi-4e51</link>
      <guid>https://dev.to/yusuf_kzlkan_bec8219b13/i-built-a-free-full-stack-auth-starter-so-you-can-skip-auth-week-flutter-fastapi-4e51</guid>
      <description>&lt;p&gt;Every side project I've started died a little during the same seven days: &lt;strong&gt;auth week.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You know the drill. Login screen, register screen, forgot password. JWT access tokens, refresh tokens, where do I even store these securely? Then the Google Sign-In configuration maze — SHA-1 fingerprints, two different client IDs, the OAuth consent screen. A week of boring, error-prone work before you write a single line of your actual product.&lt;/p&gt;

&lt;p&gt;So I built the whole thing once, properly, and released it free.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's inside
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Flutter side:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Login, Register, Forgot Password and Auth Gate screens (light/dark themes, follows system)&lt;/li&gt;
&lt;li&gt;Email/password + Google Sign-In, fully wired&lt;/li&gt;
&lt;li&gt;JWT access + refresh tokens in &lt;code&gt;flutter_secure_storage&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Automatic refresh on 401 — expired tokens refresh silently, and the user only gets logged out if the refresh itself fails&lt;/li&gt;
&lt;li&gt;Riverpod + go_router, clean feature-based architecture&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Backend side (the part most starters skip):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;FastAPI + PostgreSQL with &lt;code&gt;/auth/register&lt;/code&gt;, &lt;code&gt;/login&lt;/code&gt;, &lt;code&gt;/refresh&lt;/code&gt;, &lt;code&gt;/forgot-password&lt;/code&gt;, &lt;code&gt;/me&lt;/code&gt;, &lt;code&gt;/google&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;bcrypt password hashing, short-lived access tokens, rotating refresh tokens&lt;/li&gt;
&lt;li&gt;Starts with one command: &lt;code&gt;docker compose up&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Interactive API docs at &lt;code&gt;/docs&lt;/code&gt; out of the box (thanks, FastAPI)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why full-stack?
&lt;/h2&gt;

&lt;p&gt;Most free auth templates give you beautiful screens and leave the backend as "an exercise for the reader." Which means auth week isn't actually over — you just moved it.&lt;/p&gt;

&lt;p&gt;This starter runs end to end: clone → &lt;code&gt;docker compose up&lt;/code&gt; → &lt;code&gt;flutter run&lt;/code&gt; → sign in. Real Postgres, real tokens, real error handling (wrong password shows a clean error banner, not a crash).&lt;/p&gt;

&lt;h2&gt;
  
  
  Two gotchas I hit (so you don't have to)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Non-ASCII Windows paths break the Flutter build.&lt;/strong&gt; My desktop folder is &lt;code&gt;Masaüstü&lt;/code&gt; (Turkish Windows). Gradle and Flutter's shader compiler choke on the &lt;code&gt;ü&lt;/code&gt;. If your Windows is in Turkish, German, Spanish... and your project lives under a localized path, you'll hit this too. The fix (subst a drive letter + &lt;code&gt;android.overridePathCheck=true&lt;/code&gt;) is documented in the repo's FAQ.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. &lt;code&gt;flutter_secure_storage&lt;/code&gt; needs minSdk 23+.&lt;/strong&gt; Recent versions silently require it; older project templates default lower and the build fails with a confusing error. Bump &lt;code&gt;minSdk&lt;/code&gt; to 23 and &lt;code&gt;compileSdk&lt;/code&gt; to 36.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub (MIT licensed):&lt;/strong&gt; &lt;a href="https://github.com/yusufkizilkan/flutter-fastapi-auth-starter" rel="noopener noreferrer"&gt;https://github.com/yusufkizilkan/flutter-fastapi-auth-starter&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3-minute setup video:&lt;/strong&gt; &lt;a href="https://www.youtube.com/watch?v=x1ByQMPEy8g" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=x1ByQMPEy8g&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Download on Gumroad&lt;/strong&gt; (free, pay what you want): &lt;a href="https://kizilkan2.gumroad.com/l/flutter-auth-starter" rel="noopener noreferrer"&gt;https://kizilkan2.gumroad.com/l/flutter-auth-starter&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This starter is the foundation of a full SaaS kit I'm building in public — subscriptions (RevenueCat), push notifications, Apple Sign-In, admin panel. Star the repo if you want to follow along.&lt;/p&gt;

&lt;p&gt;I'd genuinely love feedback — especially on the token refresh flow and the folder structure. What does your auth week look like?&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>fastapi</category>
      <category>opensource</category>
      <category>android</category>
    </item>
  </channel>
</rss>
