{"id":18768538,"url":"https://github.com/pdevito3/auth-example-for-lee","last_synced_at":"2026-01-30T21:38:31.223Z","repository":{"id":110876858,"uuid":"529447961","full_name":"pdevito3/auth-example-for-lee","owner":"pdevito3","description":null,"archived":false,"fork":false,"pushed_at":"2022-08-27T01:16:23.000Z","size":183,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-13T07:45:44.934Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pdevito3.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-08-27T01:15:56.000Z","updated_at":"2024-02-12T10:30:17.000Z","dependencies_parsed_at":"2023-03-12T21:00:14.813Z","dependency_job_id":null,"html_url":"https://github.com/pdevito3/auth-example-for-lee","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/pdevito3/auth-example-for-lee","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pdevito3%2Fauth-example-for-lee","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pdevito3%2Fauth-example-for-lee/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pdevito3%2Fauth-example-for-lee/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pdevito3%2Fauth-example-for-lee/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pdevito3","download_url":"https://codeload.github.com/pdevito3/auth-example-for-lee/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pdevito3%2Fauth-example-for-lee/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28919765,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-30T20:25:28.696Z","status":"ssl_error","status_checked_at":"2026-01-30T20:25:13.426Z","response_time":66,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-07T19:13:05.314Z","updated_at":"2026-01-30T21:38:31.209Z","avatar_url":"https://github.com/pdevito3.png","language":"C#","readme":"# auth-example-for-lee\n\nThis project was created with [Craftsman](https://github.com/pdevito3/craftsman).\n\n## Getting Started\n### Docker Setup\n1. Run `docker-compose up --build` from your `.sln` directory to spin up your database(s) and other supporting \ninfrastructure depending on your configuration (e.g. RabbitMQ, Keycloak, Jaeger, etc.).\n\n### Keycloak Auth Server\n1. If using a Keycloak auth server, you'll need to use the scaffolded Pulumi setup or configure it manually (new realm, client, etc) or .\n   1. [Install the pulumi CLI](https://www.pulumi.com/docs/get-started/)\n   1. `cd` to your the `KeycloackPulumi` project directory\n   1. Run `pulumi up` to start the scaffolding process\n   1. Create a new stack by pressing `Enter` when prompted and then typing the name of the stack (e.g. `dev`). Alternatively\n      you can use the `pulumi stack init` command to make a new stack first.\n      \u003e Note: The stack name must match the extension on your yaml config file (e.g. `Pulumi.dev.yaml`) would have a stack of `dev`.\n   1. Select yes to apply the configuration to your local Keycloak instance.\n   1. Navigate to keycloak client at `localhost:3255/auth` and login with `admin` for username and password to view config (if you want). \n\n### Api\nIf you want to run your api: \n1. Make sure you have the [.NET 6 SDK](https://dotnet.microsoft.com/en-us/download/visual-studio-sdks) installed \n2. Make sure you have entity framework installed: `dotnet tool install --global dotnet-ef`\n3. Apply migrations\n    1. Confirm your environment (`ASPNETCORE_ENVIRONMENT`) is set to `Development` using \n    `$Env:ASPNETCORE_ENVIRONMENT = \"Development\"` for powershell or `export ASPNETCORE_ENVIRONMENT=Development` for bash.\n   2. `cd` to the boundary project root (e.g. `cd RecipeManagement/src/RecipeManagement`)\n   3. Run `dotnet ef database update` to apply your migrations.\n4. From the `RecipeManagement/src/RecipeManagement` directory, run the api: `dotnet run`\n5. Go to  `https://localhost:5375/swagger/index.html` to use swagger\n\n### Next JS\n1. add an `.env.local` file to the root of your next project:\n```\nNEXTAUTH_URL=http://localhost:8582\nNEXTAUTH_SECRET= 974d6f71-d41b-4601-9a7a-a33081f82188\n```\n2. `cd` to next app\n2. Run `yarn` and then `yarn dev` \n2. Go to `http://localhost:8582/`\n2. Press `Login`\n3. Enter `alice` for username and password. You will be prompted to change your password, you can change it to whatever you want.\n4. Use and explore \n\n\n\n## Summary of Gaps\n\n### Session Logout ⭐️⭐️\n\nSetup for providers is straightforward and I like that you can use a `well-known`, but you currently have to handroll your session signout with an event. If `next-auth` has a `well-known`, they should really be able to  get the `end_session_endpoint` and do it for me. Maybe backchannel notifications are the preffered way for the lib, but I'm not sure? [See here](https://youtu.be/UBFx3MSu1Rc?t=2841).\n\n```tsx\n providers: [\n    {\n      id: \"oidc\",\n      name: \"OIDC\",\n      type: \"oauth\",\n      wellKnown: `${env.clientUrls.authServer()}/.well-known/openid-configuration`,\n      authorization: {\n        params: { scope: \"openid email profile recipe_management\" },\n      },\n      idToken: true,\n      checks: [\"pkce\", \"state\"],\n      clientId: \"recipe_management.next\",\n      clientSecret: env.auth.secret,\n      profile(profile) {\n        return {\n          id: profile.sub,\n          name: profile.name,\n          email: profile.email,\n          image: profile.picture,\n        };\n      },\n    },\n  ],\n  events: {\n    async signOut({ token }) {\n      var refreshToken = token.refreshToken;\n      let headers = { \"Content-Type\": \"application/x-www-form-urlencoded\" };\n      try {\n        await clients.authServer.post(\n          `/protocol/openid-connect/logout`,\n          querystring.stringify({\n            refresh_token: refreshToken,\n            client_secret: env.auth.secret,\n            client_id: \"recipe_management.next\",\n          }),\n          { headers }\n        );\n      } catch (e) {}\n    },\n  },\n```\n\n\n\n### JWT Handling ⭐️⭐️\n\nLike the above, needing to build a JWT like below shouldn't be something required out of the box. Even worse, it's worth noting that [the example in the docs](https://next-auth.js.org/tutorials/refresh-token-rotation) isn't even using a proper calculation. I belive it should be going off of the expiration in the access token to stay in sync with the server using the OAuth standard value of `exp` in the access token (more like the below, though for all i know i have gaps -- hand rolled!).\n\n```tsx\n    async jwt({ token, user, account }) {\n      // Initial sign in\n      if (account \u0026\u0026 user) {\n        const decodedAccessToken = parseJwt(account.access_token);\n        const nextAuthToken = {\n          accessToken: account.access_token,\n          accessTokenExpires: decodedAccessToken.exp * 1000,\n          refreshToken: account.refresh_token,\n          user,\n        };\n        return nextAuthToken;\n      }\n\n      // Return previous token if the access token has not expired yet\n      if (Date.now() \u003c Number(token.accessTokenExpires)) {\n        return token;\n      }\n\n      // Access token has expired, try to update it\n      return refreshAccessToken(token);\n    },\n    async session({ session, token }) {\n      session.user = token.user as User;\n      session.accessToken = token.accessToken;\n      session.error = token.error;\n\n      return session;\n    },\n  },\n\n//....\n\nfunction parseJwt(token) {\n  return JSON.parse(Buffer.from(token.split(\".\")[1], \"base64\").toString());\n}\n\n```\n\n\n\n### Calling Apis ⭐️⭐️\n\n\u003e ✉️ This is potentially fine as is (at least for the time being) if there's documentation on it but there is nothing about this on the site at all\n\nThis was a frustrating one for me. While NextJS apis are great and have great uses caes, even so, one of the primary reasons for adding auth to many business systems is presumably to be able to call api's throughout your business and be able to authenticate with those systems.\n\nWhen using `next-auth`, it encodes the token when storing it and, as far as i know and have seen from resources that is great, but is not able to decode the token and proxy API calls to your api endpoints.\n\nCurrently, I need to get the token from my session and pass it as a `Bearer` for every call. Personally made an axios client like this to make it a bit easier, but feels wrong to me.\n\n```tsx\nimport { env } from \"@/utils/environmentService\";\nimport Axios from \"axios\";\nimport { getSession, signOut } from \"next-auth/react\";\n\nexport const clients = {\n  recipeManagement: (headers?: { [key: string]: string }) =\u003e\n    buildApiClient({\n      baseURL: `${env.clientUrls.recipeManagement()}/api`,\n      customHeaders: headers,\n    }),\n  authServer: Axios.create({\n    baseURL: env.clientUrls.authServer(),\n  }),\n};\n\ninterface ApiClientProps {\n  baseURL?: string;\n  customHeaders?: {\n    [key: string]: string;\n  };\n}\n\nasync function buildApiClient({ baseURL, customHeaders }: ApiClientProps) {\n  var session = await getSession();\n  var token = session?.accessToken;\n\n  const client = Axios.create({\n    baseURL,\n    withCredentials: true,\n    timeout: 30_000, // If you want to increase this, do it for a specific call, not the global app API.\n    headers: {\n      \"X-CSRF\": \"1\",\n      Authorization: `Bearer ${token}`,\n      ...customHeaders,\n    },\n  });\n\n  client.interceptors.response.use(\n    (response) =\u003e response,\n    async (error) =\u003e {\n      if (error.response) {\n        // The request was made and the server responded with a status code\n        // that falls out of the range of 2xx\n        console.error(\n          error.response.status,\n          error.response.data,\n          error.response.headers\n        );\n      } else if (error.request) {\n        // The request was made but no response was received\n        // `error.request` is an instance of XMLHttpRequest in the browser and an instance of\n        // http.ClientRequest in node.js\n        console.error(error.request);\n      }\n\n      if (error \u0026\u0026 error.response \u0026\u0026 error.response.status === 401) {\n        signOut();\n      }\n      console.log((error \u0026\u0026 error.toJSON \u0026\u0026 error.toJSON()) || undefined);\n\n      return Promise.reject(error);\n    }\n  );\n\n  return client;\n}\n\n```\n\n\n\n#### Proposed Enhancement\n\nDuende has [a solid method](https://docs.duendesoftware.com/identityserver/v5/bff/apis/remote/#use-our-built-in-simple-http-forwarder) for proxying enpoints using YARP. I'd propose a similar alternative witht this and the above documented. Guessing this might have to go in middleware, but this type of setup would add more control over your endpoints \n\n```tsx\nexport const authOptions: NextAuthOptions = {\n  // https://next-auth.js.org/configuration/providers/oauth\n  providers: [\n    {\n      id: \"oidc\",\n      name: \"OIDC\",\n      type: \"oauth\",\n      wellKnown: `${env.clientUrls.authServer()}/.well-known/openid-configuration`,\n      authorization: {\n        params: { scope: \"openid email profile recipe_management\" },\n      },\n      idToken: true,\n      checks: [\"pkce\", \"state\"],\n      clientId: \"recipe_management.next\",\n      clientSecret: env.auth.secret,\n      profile(profile) {\n        return {\n          id: profile.sub,\n          name: profile.name,\n          email: profile.email,\n          image: profile.picture,\n        };\n      },\n    },\n  ],\n  events: {},\n  callbacks: {},\n  cookies: {},\n  remoteEndpoints: {\n    proxy(endpoints) {\n    \tendpoints.MapRemoteBffApiEndpoint(\"/api/customers\", \"https://remoteHost/customers\")\n        .RequireAccessToken(TokenType.User); // this could be `User` for code flow, `Client` for Client Credentials flow, or `TokenType.UserOrClient` for either.\n\t  }\n  },\n};\n\n```\n\n\n\n### Security Enhancements ⭐️\n\n#### Anti-forgery protection\n\n[Protect apis with a custom header](https://youtu.be/UBFx3MSu1Rc?t=3024). Could be anything, but having a custom header will force the browser to kick off a CORS preflight request. Duende enforces this with middleware, so maybe that's something that can be added in next middleware?\n\n\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpdevito3%2Fauth-example-for-lee","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpdevito3%2Fauth-example-for-lee","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpdevito3%2Fauth-example-for-lee/lists"}