Skip to content

OAuth2 Authentication System#1711

Open
PopovMarko wants to merge 16 commits into
RezaSi:mainfrom
PopovMarko:challenge-15-marko
Open

OAuth2 Authentication System#1711
PopovMarko wants to merge 16 commits into
RezaSi:mainfrom
PopovMarko:challenge-15-marko

Conversation

@PopovMarko

Copy link
Copy Markdown
Contributor

Implemeented Server that supports OAuth authorization code flow, allowing third party application to authenticate users

@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 93ac39ca-a01d-4fac-8c3a-f3313ed184b0

📥 Commits

Reviewing files that changed from the base of the PR and between 377ed57 and fbac9a2.

📒 Files selected for processing (1)
  • challenge-15/submissions/PopovMarko/solution-template.go

Walkthrough

Self-contained in-memory Go OAuth2 server and client: /authorize and /token endpoints, PKCE support, authorization-code issuance/consumption, refresh-token rotation, token validation/revocation, HTTP wiring, and demo main.

Changes

OAuth2 Server and Client Implementation

Layer / File(s) Summary
OAuth2 Server domain model and initialization
challenge-15/submissions/PopovMarko/solution-template.go
Defines exported error sentinels and types, in-memory maps for clients/auth codes/tokens/refresh tokens/users with RW mutex protection, NewOAuth2Server, RegisterClient, and GenerateRandomString.
Authorization endpoint and code generation
challenge-15/submissions/PopovMarko/solution-template.go
HandleAuthorize parses GET params, validates user/client/redirect URI/response_type/scope and PKCE presence/method; unsupported response_type triggers OAuth2-style redirect error; GenerateAuthCode issues time-limited auth codes bound to client/user/redirect/scopes and stores PKCE data; redirect helpers included.
Token endpoint plumbing and request parsing
challenge-15/submissions/PopovMarko/solution-template.go
HandleToken parses application/x-www-form-urlencoded POSTs, dispatches by grant_type, and serializes OAuth2 JSON errors/success responses; TokenRequestDTO and form extraction helpers added.
authorization_code grant handling
challenge-15/submissions/PopovMarko/solution-template.go
Validates auth-code existence/expiry, client registration/id/secret, redirect URI, and PKCE verifier under lock; consumes the auth code, mints/stores access+refresh tokens, and returns a Bearer token JSON response.
refresh_token grant handling and rotation
challenge-15/submissions/PopovMarko/solution-template.go
Validates refresh-token existence/expiry and client credentials, performs refresh-token rotation by deleting the old refresh token and storing new tokens, and returns rotated token JSON.
Token lifecycle management and PKCE verification
challenge-15/submissions/PopovMarko/solution-template.go
generateTokens, ValidateToken (unknown vs expired mapping), RefreshAccessToken rotation helper, RevokeToken deletion logic, and VerifyCodeChallenge implementing PKCE S256 and plain.
Server HTTP setup and client-side OAuth2 library
challenge-15/submissions/PopovMarko/solution-template.go
StartServer registers /authorize and /token handlers and starts the HTTP listener. OAuth2Client builds PKCE authorization URLs, exchanges codes for tokens, refreshes tokens, makes Bearer-authenticated requests, and decodes token JSON responses.
Demo application
challenge-15/submissions/PopovMarko/solution-template.go
main initializes server, registers a sample client, starts the server on port 9000 in a goroutine, and prints startup output with example usage commented.

Sequence Diagram(s)

sequenceDiagram
  participant Client as OAuth2Client
  participant Server as OAuth2Server
  participant Store as InMemoryStore
  Client->>Server: GET /authorize (client_id, scope, code_challenge, ...)
  Server->>Store: GenerateAuthCode (store code with client,user,PKCE)
  Server-->>Client: Redirect with authorization_code or error
  Client->>Server: POST /token (grant_type=authorization_code, code, code_verifier)
  Server->>Store: consume auth code (delete) then validate (expiry, client, redirect, PKCE)
  Server->>Store: generate and store access_token + refresh_token
  Server-->>Client: JSON token response
  Client->>Server: POST /token (grant_type=refresh_token, refresh_token)
  Server->>Store: validate and rotate refresh token, store new tokens
  Server-->>Client: JSON rotated tokens
  Client->>Server: GET /protected (Authorization: Bearer access_token)
  Server-->>Client: 200 OK protected resource
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hop through auth with a cheerful cheer,
Codes and tokens scurry far and near,
PKCE knots tied with a careful rhyme,
Refreshes whirl and rotate in time,
In-memory demo — a rabbit's rhyme.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'OAuth2 Authentication System' directly relates to the main change: a complete OAuth2 server implementation with authorization code flow, matching the PR's primary objective.
Description check ✅ Passed The description 'Implemented Server that supports OAuth authorization code flow, allowing third party application to authenticate users' is directly related to the changeset, which adds a complete OAuth2 server implementation.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0ecdd7a6-0d02-4db0-90e9-2390ffeb2aed

📥 Commits

Reviewing files that changed from the base of the PR and between aca35dc and c5dcf60.

📒 Files selected for processing (1)
  • challenge-15/submissions/PopovMarko/solution-template.go

Comment thread challenge-15/submissions/PopovMarko/solution-template.go
Comment thread challenge-15/submissions/PopovMarko/solution-template.go
Comment thread challenge-15/submissions/PopovMarko/solution-template.go Outdated
Comment thread challenge-15/submissions/PopovMarko/solution-template.go Outdated
Comment thread challenge-15/submissions/PopovMarko/solution-template.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 92fc4896-a11d-4e48-9e24-73c5ff051421

📥 Commits

Reviewing files that changed from the base of the PR and between c5dcf60 and 7e50ea4.

📒 Files selected for processing (1)
  • challenge-15/submissions/PopovMarko/solution-template.go

Comment thread challenge-15/submissions/PopovMarko/solution-template.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
challenge-15/submissions/PopovMarko/solution-template.go (1)

558-560: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Missing return after client-ID mismatch error.

Line 559 writes an error response but execution continues. If the client secret happens to match, tokens are issued despite the client-ID mismatch—a token leakage / authorization bypass.

Proposed fix
 	if dto.clientID != client.ClientID {
 		tokenErrorResponse(w, "client not found", ErrInvalidClient)
+		return
 	}
🧹 Nitpick comments (1)
challenge-15/submissions/PopovMarko/solution-template.go (1)

672-672: 💤 Low value

Remove stale TODO comment.

The PKCE verification is fully implemented below, but the TODO suggests it isn't.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: aac49e61-15b9-4d6d-b4ed-6bd44e45499e

📥 Commits

Reviewing files that changed from the base of the PR and between 7e50ea4 and 243e233.

📒 Files selected for processing (1)
  • challenge-15/submissions/PopovMarko/solution-template.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
challenge-15/submissions/PopovMarko/solution-template.go (1)

281-288: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Use uri.Query() instead of r.URL.Query() to preserve redirect URI params.

Line 283 reads query parameters from the incoming authorization request (r.URL.Query()), which copies all the authorize endpoint's params (client_id, redirect_uri, scope, code_challenge, etc.) onto the callback URL. This violates the OAuth2 spec and loses any query params already present on the registered redirect URI.

The callback should only contain the OAuth2 response params (code/error and state) plus any params originally on the registered redirect URI (e.g., ?tenant=acme).

🔧 Proposed fix
 func authResponse(w http.ResponseWriter, r *http.Request, redirectUri string, response map[string]string) {
 	uri, _ := url.Parse(redirectUri)
-	val := r.URL.Query()
+	val := uri.Query()
 	for k, v := range response {
 		val.Set(k, v)
 	}
 	uri.RawQuery = val.Encode()
 	http.Redirect(w, r, uri.String(), http.StatusFound)
 }

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3a524ed3-93dc-47a9-95a3-59a4a4f14d69

📥 Commits

Reviewing files that changed from the base of the PR and between 243e233 and 7a1d9f5.

📒 Files selected for processing (1)
  • challenge-15/submissions/PopovMarko/solution-template.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
challenge-15/submissions/PopovMarko/solution-template.go (2)

299-302: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Reject unauthenticated /authorize requests instead of defaulting to user1.

Any request that arrives without an authenticated user context currently gets an auth code for the preloaded user1, which means a third-party app can mint tokens without a real login step.

Suggested fix
 func getAuthRequestParams(r *http.Request) AuthRequestDTO {
 	query := r.URL.Query()

 	userID, _ := r.Context().Value("user_id").(string)
-	if userID == "" {
-		userID = "user1"
-	}

 	method := query.Get("code_challenge_method")
 	if method == "" {
 		method = "plain"
@@
 func (s *OAuth2Server) validAuthRequestParams(dto AuthRequestDTO) error {
 	s.mu.RLock()
 	defer s.mu.RUnlock()
+	if dto.userID == "" {
+		return fmt.Errorf("user not authenticated: %w", ErrInvalidRequest)
+	}
 	if _, exists := s.users[dto.userID]; !exists {
 		return fmt.Errorf("user not found %w", ErrInvalidRequest)
 	}

Also applies to: 323-328


610-620: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Return copies instead of the server’s live token records.

Both exported methods hand out the exact structs stored in the in-memory maps. Callers can mutate ExpiresAt, ClientID, or Scopes and change the server’s authorization state from outside the mutex.

Suggested fix
+func cloneToken(t *Token) *Token {
+	if t == nil {
+		return nil
+	}
+	cp := *t
+	cp.Scopes = append([]string(nil), t.Scopes...)
+	return &cp
+}
+
+func cloneRefreshToken(t *RefreshToken) *RefreshToken {
+	if t == nil {
+		return nil
+	}
+	cp := *t
+	cp.Scopes = append([]string(nil), t.Scopes...)
+	return &cp
+}
+
 func (s *OAuth2Server) ValidateToken(token string) (*Token, error) {
 	s.mu.RLock()
 	accToken, ok := s.tokens[token]
-	s.mu.RUnlock()
 	if !ok {
+		s.mu.RUnlock()
 		return nil, fmt.Errorf("token not found: %w", ErrInvalidRequest)
 	}
 	if accToken.ExpiresAt.Before(time.Now()) {
+		s.mu.RUnlock()
 		return nil, fmt.Errorf("token expired: %w", ErrInvalidGrant)
 	}
-	return accToken, nil
+	out := cloneToken(accToken)
+	s.mu.RUnlock()
+	return out, nil
 }
@@
-	return accToken, newRefToken, nil
+	return cloneToken(accToken), cloneRefreshToken(newRefToken), nil
 }

Also applies to: 626-648

♻️ Duplicate comments (1)
challenge-15/submissions/PopovMarko/solution-template.go (1)

626-646: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Make RefreshAccessToken rotation atomic too.

This helper still does lookup/validation under RLock and only deletes the old refresh token later under Lock, so two concurrent callers can both rotate the same single-use refresh token.

Suggested fix
 func (s *OAuth2Server) RefreshAccessToken(refreshToken string) (*Token, *RefreshToken, error) {
-	s.mu.RLock()
+	s.mu.Lock()
+	defer s.mu.Unlock()
+
 	refToken, ok := s.refreshTokens[refreshToken]
-	s.mu.RUnlock()
 	if !ok {
 		return nil, nil, fmt.Errorf("refresh token not found: %w", ErrInvalidGrant)
 	}
 	if refToken.ExpiresAt.Before(time.Now()) {
 		return nil, nil, fmt.Errorf("refresh token expired: %w", ErrInvalidGrant)
@@
-	s.mu.Lock()
 	delete(s.refreshTokens, refreshToken)
 	s.tokens[accToken.AccessToken] = accToken
 	s.refreshTokens[newRefToken.RefreshToken] = newRefToken
-	s.mu.Unlock()

 	return accToken, newRefToken, nil
 }

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 40d99f00-52f3-45c9-af2c-ad4eba74edbd

📥 Commits

Reviewing files that changed from the base of the PR and between 7a1d9f5 and 354ff27.

📒 Files selected for processing (1)
  • challenge-15/submissions/PopovMarko/solution-template.go

@PopovMarko

Copy link
Copy Markdown
Contributor Author

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Reviews resumed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e22458c1-aa36-43d0-8714-73fdfc1c5b6b

📥 Commits

Reviewing files that changed from the base of the PR and between 7a1d9f5 and 377ed57.

📒 Files selected for processing (1)
  • challenge-15/submissions/PopovMarko/solution-template.go

Comment thread challenge-15/submissions/PopovMarko/solution-template.go
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant