Skip to content

Commit e10342c

Browse files
committed
refactor: migrate to deicod/oidcmw for OIDC and viewer
Replaced custom OIDC middleware and viewer implementations with the external github.com/deicod/oidcmw package to simplify code and leverage standardized functionality. Updated imports in cmd/graphql.go and rules files, refactored middleware setup, and adjusted viewer method calls. Removed obsolete custom packages and related documentation from README.md.
1 parent bc0b4e9 commit e10342c

31 files changed

+776
-1388
lines changed

README.md

Lines changed: 4 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ This is here to make things easier and help people get started with GraphQL and
77
## Assumptions
88

99
- You use [Keycloak](https://www.keycloak.org/) as the OIDC IDP.
10-
It can of course be used with any IDP. Just the claims struct is the default that Keycloak uses.
10+
It can be used with any IDP, but a custom viewer would need to be constructed.
1111
You will have to add the audience in Keycloak to the token, because Keycloak is dumb like that.
1212
- [xid](https://github.com/rs/xid) is used for globally unique IDs
1313
- The `Profile` schema is the root of your related entities.
@@ -128,40 +128,9 @@ go generate ./...
128128

129129
## OIDC
130130

131-
### Claims
132-
133-
This package assumes Keycloak being the OIDC IDP.
134-
Therefore the [claims object](rules/claims/claims.go) reflects Keycloak's claim structure.
135-
Change this to your claim structure.
136-
For instance I'm using Zitadel, adding per project grants and flattening roles and adding them to the root level as `roles`.
137-
The claims structure looks like this:
138-
139-
```go
140-
type Claims struct {
141-
Aud []string `json:"aud"`
142-
Exp time.Time `json:"exp"`
143-
Iat time.Time `json:"iat"`
144-
Iss string `json:"iss"`
145-
Jti string `json:"jti"`
146-
Nbf time.Time `json:"nbf"`
147-
Roles []string `json:"roles"`
148-
Sub string `json:"sub"`
149-
}
150-
```
151-
152-
### Viewer and Middleware
153-
154-
This template now derives a typed `Viewer` from the OIDC claims and attaches it to the request context via middleware:
155-
156-
- `rules/viewer/viewer.go`: defines `Viewer` with helpers like `IsAuthenticated()`, `Subject()`, and `HasRole()`.
157-
- `middleware/viewer.go`: HTTP middleware that reads claims from context (populated by the OIDC middleware) and stores a `Viewer` in context.
158-
- Ent privacy rules and hooks prefer `Viewer` when present and fall back to raw claims.
159-
160-
If you want to require auth for reads, remove `options.IsPermissive()` in `cmd/graphql.go`.
161-
162-
### Making read access require auth
163-
164-
On line 79 in `cmd/graphql.go` remove the `options.IsPermissive(),`.
131+
Uses [OIDC Middleware Guard](https://github.com/deicod/oidcmw) and its viewer with helper functions.
132+
It assumes a Keycloak claims structure.
133+
If you need the claims `viewer.RawClaims` which returns a `map[string]any`.
165134

166135
## Further reading
167136

cmd/graphql.go

Lines changed: 39 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ import (
1212
"strings"
1313
"time"
1414

15-
"code.icod.de/dalu/nethttpoidc"
16-
"code.icod.de/dalu/oidc/options"
1715
"entgo.io/contrib/entgql"
1816
"entgo.io/ent/dialect"
1917
entsql "entgo.io/ent/dialect/sql"
@@ -24,12 +22,12 @@ import (
2422
"github.com/99designs/gqlgen/graphql/handler/transport"
2523
"github.com/99designs/gqlgen/graphql/playground"
2624
"github.com/MadAppGang/httplog"
25+
oidcCfg "github.com/deicod/oidcmw/config"
26+
oidcMW "github.com/deicod/oidcmw/middleware"
27+
"github.com/deicod/oidcmw/viewer"
2728
"github.com/dlukt/graphql-backend-starter/config"
2829
"github.com/dlukt/graphql-backend-starter/ent"
2930
"github.com/dlukt/graphql-backend-starter/graph"
30-
"github.com/dlukt/graphql-backend-starter/middleware"
31-
"github.com/dlukt/graphql-backend-starter/rules/claims"
32-
"github.com/dlukt/graphql-backend-starter/rules/viewer"
3331
"github.com/gorilla/websocket"
3432
"github.com/rs/cors"
3533
"github.com/spf13/cobra"
@@ -73,34 +71,54 @@ var graphqlCmd = &cobra.Command{
7371
log.Fatal("opening ent client", e)
7472
}
7573

74+
var oidcConfig oidcCfg.Config
75+
76+
if graphqlDebug {
77+
oidcConfig = oidcCfg.Config{
78+
Issuer: config.OidcConfigDev.Issuer,
79+
Audiences: []string{
80+
"account",
81+
config.OidcConfigDev.Audience,
82+
},
83+
AuthorizedParties: []string{
84+
config.OidcConfigDev.AuthorizedParty,
85+
},
86+
AllowAnonymousRequests: true,
87+
}
88+
} else {
89+
oidcConfig = oidcCfg.Config{
90+
Issuer: config.OidcConfigProd.Issuer,
91+
Audiences: []string{
92+
"account",
93+
config.OidcConfigProd.Audience,
94+
},
95+
AuthorizedParties: []string{
96+
config.OidcConfigProd.AuthorizedParty,
97+
},
98+
AllowAnonymousRequests: true,
99+
}
100+
}
101+
mw, e := oidcMW.NewMiddleware(oidcConfig)
102+
if e != nil {
103+
return e
104+
}
76105
srv := NewDefaultServer(graph.NewSchema(client))
77106
srv.Use(entgql.Transactioner{TxOpener: client})
78107

79-
cfg := config.OidcConfigDev
80-
81-
// Compose middleware: OIDC first, then Viewer, then GraphQL server.
82-
viewerHandler := middleware.WithViewer(srv)
83-
oidcHandler := nethttpoidc.New(viewerHandler,
84-
options.WithIssuer(cfg.Issuer),
85-
options.WithRequiredTokenType("JWT"),
86-
options.WithRequiredAudience(cfg.Audience),
87-
options.IsPermissive(),
88-
)
89-
90108
corsHandler := cors.AllowAll()
91109
fmt.Println("debug:", graphqlDebug)
92110
if !graphqlDebug {
93111
http.Handle("/query", corsHandler.Handler(
94112
httplog.HandlerWithFormatter(
95113
httplog.DefaultLogFormatter,
96-
oidcHandler,
114+
mw(srv),
97115
)))
98116
} else {
99117
http.Handle("/", playground.Handler("graphql", "/query"))
100118
http.Handle("/query", corsHandler.Handler(
101119
httplog.HandlerWithFormatter(
102120
httplog.DefaultLogFormatterWithRequestHeader,
103-
oidcHandler,
121+
mw(srv),
104122
)))
105123
}
106124

@@ -190,10 +208,9 @@ func NewDefaultServer(es graphql.ExecutableSchema) *handler.Server {
190208
}
191209
token := strings.TrimPrefix(auth, "Bearer ")
192210
if m := decodeJWTClaims(token); m != nil {
193-
ctx = context.WithValue(ctx, options.DefaultClaimsContextKeyName, m)
194-
c := claimsFromMap(m)
195-
v := viewer.NewFromClaims(c)
196-
ctx = viewer.NewContext(ctx, v)
211+
ctx = context.WithValue(ctx, "claims", m)
212+
v := viewer.FromClaims(m)
213+
ctx = viewer.WithViewer(ctx, v)
197214
}
198215
return ctx, nil, nil
199216
},
@@ -239,15 +256,3 @@ func decodeJWTClaims(token string) map[string]any {
239256
}
240257
return out
241258
}
242-
243-
func claimsFromMap(m map[string]any) *claims.Claims {
244-
j, err := json.Marshal(m)
245-
if err != nil {
246-
return nil
247-
}
248-
var c claims.Claims
249-
if err := json.Unmarshal(j, &c); err != nil {
250-
return nil
251-
}
252-
return &c
253-
}

config/oidc.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
package config
22

33
type OIDCConfig struct {
4-
Issuer string
5-
Audience string
4+
Issuer string
5+
Audience string
6+
AuthorizedParty string
67
}
78

89
var OidcConfigDev = OIDCConfig{
9-
Issuer: "https://localhost:8080/auth/realms/starter",
10-
Audience: "dev",
10+
Issuer: "https://auth.icod.de/realms/dev",
11+
Audience: "spa",
12+
AuthorizedParty: "spa",
1113
}
1214

1315
var OidcConfigProd = OIDCConfig{
14-
Issuer: "https://localhost:8080/auth/realms/starter",
15-
Audience: "spa",
16+
Issuer: "https://auth.icod.de/realms/dev",
17+
Audience: "spa",
18+
AuthorizedParty: "spa",
1619
}

ent/client.go

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ent/ent.go

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ent/entql.go

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ent/gql_collection.go

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)