nextjs-secure 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -56,6 +56,12 @@ pnpm add nextjs-secure
56
56
  - [Quick Start](#quick-start-1)
57
57
  - [Presets](#presets)
58
58
  - [Custom Configuration](#custom-configuration)
59
+ - [Authentication](#authentication)
60
+ - [JWT Authentication](#jwt-authentication)
61
+ - [API Key Authentication](#api-key-authentication)
62
+ - [Session Authentication](#session-authentication)
63
+ - [Role-Based Access Control](#role-based-access-control)
64
+ - [Combined Authentication](#combined-authentication)
59
65
  - [Utilities](#utilities)
60
66
  - [API Reference](#api-reference)
61
67
  - [Examples](#examples)
@@ -630,6 +636,282 @@ export async function GET() {
630
636
  | Cross-Origin-Embedder-Policy | Controls embedding |
631
637
  | Cross-Origin-Resource-Policy | Controls resource sharing |
632
638
 
639
+ ## Authentication
640
+
641
+ Flexible authentication middleware supporting JWT, API keys, session cookies, and role-based access control.
642
+
643
+ ### JWT Authentication
644
+
645
+ ```typescript
646
+ import { withJWT } from 'nextjs-secure/auth'
647
+
648
+ export const GET = withJWT(
649
+ async (req, ctx) => {
650
+ // ctx.user contains the authenticated user
651
+ return Response.json({ user: ctx.user })
652
+ },
653
+ {
654
+ secret: process.env.JWT_SECRET,
655
+ // or use publicKey for RS256/ES256
656
+ }
657
+ )
658
+ ```
659
+
660
+ #### Configuration
661
+
662
+ ```typescript
663
+ export const GET = withJWT(handler, {
664
+ // Secret for HMAC algorithms (HS256, HS384, HS512)
665
+ secret: process.env.JWT_SECRET,
666
+
667
+ // Public key for RSA/ECDSA (RS256, ES256, etc.)
668
+ publicKey: process.env.JWT_PUBLIC_KEY,
669
+
670
+ // Allowed algorithms (default: ['HS256'])
671
+ algorithms: ['HS256', 'RS256'],
672
+
673
+ // Validate issuer
674
+ issuer: 'https://myapp.com',
675
+ // or multiple issuers
676
+ issuer: ['https://auth.myapp.com', 'https://api.myapp.com'],
677
+
678
+ // Validate audience
679
+ audience: 'my-api',
680
+
681
+ // Clock tolerance in seconds (for exp/nbf claims)
682
+ clockTolerance: 30,
683
+
684
+ // Custom token extraction
685
+ getToken: (req) => req.headers.get('x-auth-token'),
686
+
687
+ // Custom user mapping from JWT payload
688
+ mapUser: (payload) => ({
689
+ id: payload.sub,
690
+ email: payload.email,
691
+ roles: payload.roles || [],
692
+ }),
693
+ })
694
+ ```
695
+
696
+ ### API Key Authentication
697
+
698
+ ```typescript
699
+ import { withAPIKey } from 'nextjs-secure/auth'
700
+
701
+ export const GET = withAPIKey(
702
+ async (req, ctx) => {
703
+ return Response.json({ user: ctx.user })
704
+ },
705
+ {
706
+ validate: async (apiKey, req) => {
707
+ // Return user object if valid, null if invalid
708
+ const user = await db.users.findByApiKey(apiKey)
709
+ return user || null
710
+ },
711
+ }
712
+ )
713
+ ```
714
+
715
+ #### Configuration
716
+
717
+ ```typescript
718
+ export const GET = withAPIKey(handler, {
719
+ // Required: validation function
720
+ validate: async (apiKey, req) => {
721
+ // Lookup API key and return user or null
722
+ return db.apiKeys.findUser(apiKey)
723
+ },
724
+
725
+ // Header name (default: 'x-api-key')
726
+ headerName: 'x-api-key',
727
+
728
+ // Query parameter name (default: 'api_key')
729
+ queryParam: 'api_key',
730
+ })
731
+ ```
732
+
733
+ API keys can be sent via header or query parameter:
734
+ ```bash
735
+ # Via header
736
+ curl -H "x-api-key: YOUR_API_KEY" https://api.example.com/data
737
+
738
+ # Via query parameter
739
+ curl https://api.example.com/data?api_key=YOUR_API_KEY
740
+ ```
741
+
742
+ ### Session Authentication
743
+
744
+ ```typescript
745
+ import { withSession } from 'nextjs-secure/auth'
746
+
747
+ export const GET = withSession(
748
+ async (req, ctx) => {
749
+ return Response.json({ user: ctx.user })
750
+ },
751
+ {
752
+ validate: async (sessionId, req) => {
753
+ // Return user object if session valid, null if invalid
754
+ const session = await db.sessions.find(sessionId)
755
+ return session?.user || null
756
+ },
757
+ }
758
+ )
759
+ ```
760
+
761
+ #### Configuration
762
+
763
+ ```typescript
764
+ export const GET = withSession(handler, {
765
+ // Required: session validation function
766
+ validate: async (sessionId, req) => {
767
+ const session = await redis.get(`session:${sessionId}`)
768
+ if (!session) return null
769
+ return JSON.parse(session)
770
+ },
771
+
772
+ // Cookie name (default: 'session')
773
+ cookieName: 'session',
774
+ })
775
+ ```
776
+
777
+ ### Role-Based Access Control
778
+
779
+ Use `withRoles` after an authentication middleware to enforce role/permission requirements.
780
+
781
+ ```typescript
782
+ import { withJWT, withRoles } from 'nextjs-secure/auth'
783
+
784
+ // Chain with JWT auth
785
+ const authenticatedHandler = withJWT(
786
+ withRoles(
787
+ async (req, ctx) => {
788
+ return Response.json({ admin: true })
789
+ },
790
+ { roles: ['admin'] }
791
+ ),
792
+ { secret: process.env.JWT_SECRET }
793
+ )
794
+
795
+ export const GET = authenticatedHandler
796
+ ```
797
+
798
+ #### Configuration
799
+
800
+ ```typescript
801
+ withRoles(handler, {
802
+ // Required roles (any match = authorized)
803
+ roles: ['admin', 'moderator'],
804
+
805
+ // Required permissions (all must match)
806
+ permissions: ['read', 'write'],
807
+
808
+ // Custom role extraction
809
+ getUserRoles: (user) => user.roles || [],
810
+
811
+ // Custom permission extraction
812
+ getUserPermissions: (user) => user.permissions || [],
813
+
814
+ // Custom authorization logic
815
+ authorize: async (user, req) => {
816
+ // Return true if authorized, false otherwise
817
+ return user.subscriptionTier === 'pro'
818
+ },
819
+ })
820
+ ```
821
+
822
+ ### Combined Authentication
823
+
824
+ Use `withAuth` for flexible multi-strategy authentication:
825
+
826
+ ```typescript
827
+ import { withAuth } from 'nextjs-secure/auth'
828
+
829
+ export const GET = withAuth(
830
+ async (req, ctx) => {
831
+ // Authenticated via any method
832
+ return Response.json({ user: ctx.user })
833
+ },
834
+ {
835
+ // Try JWT first
836
+ jwt: {
837
+ secret: process.env.JWT_SECRET,
838
+ },
839
+
840
+ // Fall back to API key
841
+ apiKey: {
842
+ validate: (key) => db.apiKeys.findUser(key),
843
+ },
844
+
845
+ // Fall back to session
846
+ session: {
847
+ validate: (id) => db.sessions.findUser(id),
848
+ },
849
+
850
+ // Optional RBAC
851
+ rbac: {
852
+ roles: ['user', 'admin'],
853
+ },
854
+
855
+ // Callbacks
856
+ onSuccess: async (req, user) => {
857
+ // Log successful auth
858
+ console.log(`Authenticated: ${user.id}`)
859
+ },
860
+
861
+ onError: (req, error) => {
862
+ // Custom error response
863
+ return Response.json({ error: error.message }, { status: error.status })
864
+ },
865
+ }
866
+ )
867
+ ```
868
+
869
+ ### Optional Authentication
870
+
871
+ For routes that work with or without authentication:
872
+
873
+ ```typescript
874
+ import { withOptionalAuth } from 'nextjs-secure/auth'
875
+
876
+ export const GET = withOptionalAuth(
877
+ async (req, ctx) => {
878
+ if (ctx.user) {
879
+ // Authenticated user
880
+ return Response.json({ user: ctx.user })
881
+ }
882
+ // Anonymous access
883
+ return Response.json({ guest: true })
884
+ },
885
+ {
886
+ jwt: { secret: process.env.JWT_SECRET },
887
+ }
888
+ )
889
+ ```
890
+
891
+ ### JWT Utilities
892
+
893
+ ```typescript
894
+ import { verifyJWT, decodeJWT, extractBearerToken } from 'nextjs-secure/auth'
895
+
896
+ // Verify and decode JWT
897
+ const { payload, error } = await verifyJWT(token, {
898
+ secret: process.env.JWT_SECRET,
899
+ issuer: 'myapp',
900
+ })
901
+
902
+ if (error) {
903
+ console.log(error.code) // 'expired_token', 'invalid_signature', etc.
904
+ }
905
+
906
+ // Decode without verification (for inspection only)
907
+ const decoded = decodeJWT(token)
908
+ // { header, payload, signature }
909
+
910
+ // Extract token from Authorization header
911
+ const token = extractBearerToken(req.headers.get('authorization'))
912
+ // 'Bearer xxx' -> 'xxx'
913
+ ```
914
+
633
915
  ## Utilities
634
916
 
635
917
  ### Duration Parsing
@@ -857,14 +1139,23 @@ export const POST = withRateLimit(
857
1139
  - [x] Memory store
858
1140
  - [x] Redis store
859
1141
  - [x] Upstash store
860
- - [ ] Authentication (v0.2.0)
861
- - [ ] JWT validation
862
- - [ ] Supabase provider
863
- - [ ] NextAuth provider
864
- - [ ] Clerk provider
865
- - [ ] RBAC support
866
- - [ ] CSRF Protection (v0.3.0)
867
- - [ ] Security Headers (v0.4.0)
1142
+ - [x] CSRF Protection (v0.2.0)
1143
+ - [x] Double submit cookie pattern
1144
+ - [x] Token generation/validation
1145
+ - [x] Configurable cookie settings
1146
+ - [x] Security Headers (v0.3.0)
1147
+ - [x] Content-Security-Policy
1148
+ - [x] Strict-Transport-Security
1149
+ - [x] X-Frame-Options, X-Content-Type-Options
1150
+ - [x] Permissions-Policy
1151
+ - [x] COOP, COEP, CORP
1152
+ - [x] Presets (strict, relaxed, api)
1153
+ - [x] Authentication (v0.4.0)
1154
+ - [x] JWT validation (HS256, RS256, ES256)
1155
+ - [x] API Key authentication
1156
+ - [x] Session/Cookie authentication
1157
+ - [x] Role-Based Access Control (RBAC)
1158
+ - [x] Combined multi-strategy auth
868
1159
  - [ ] Input Validation (v0.5.0)
869
1160
  - [ ] Audit Logging (v0.6.0)
870
1161