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 +299 -8
- package/dist/auth.cjs +500 -6
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +180 -19
- package/dist/auth.d.ts +180 -19
- package/dist/auth.js +493 -6
- package/dist/auth.js.map +1 -1
- package/dist/index.cjs +2406 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2365 -2
- package/dist/index.js.map +1 -1
- package/dist/path-BVbunPfR.d.cts +534 -0
- package/dist/path-BVbunPfR.d.ts +534 -0
- package/dist/validation.cjs +2031 -0
- package/dist/validation.cjs.map +1 -0
- package/dist/validation.d.cts +42 -0
- package/dist/validation.d.ts +42 -0
- package/dist/validation.js +1964 -0
- package/dist/validation.js.map +1 -0
- package/package.json +14 -1
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
|
-
- [
|
|
861
|
-
- [
|
|
862
|
-
- [
|
|
863
|
-
- [
|
|
864
|
-
|
|
865
|
-
- [
|
|
866
|
-
- [
|
|
867
|
-
- [
|
|
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
|
|