or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

base-service.mdfile-service.mdfulfillment-service.mdindex.mdnotification-service.mdoauth-service.mdpayment-service.mdsearch-service.md

oauth-service.mddocs/

0

# OAuth Service

1

2

Interface for OAuth service implementations providing token generation, refresh, and destruction operations for OAuth-based authentication flows.

3

4

## Capabilities

5

6

### Static Methods

7

8

Type checking and identification methods for OAuth services.

9

10

```javascript { .api }

11

/**

12

* Static property identifying this as an OAuth service

13

*/

14

static _isOauthService: boolean;

15

16

/**

17

* Checks if an object is an OAuth service

18

* @param {object} obj - Object to check

19

* @returns {boolean} True if obj is an OAuth service

20

*/

21

static isOauthService(obj: object): boolean;

22

```

23

24

### Core OAuth Operations

25

26

Abstract methods that must be implemented by child classes for OAuth token management.

27

28

```javascript { .api }

29

/**

30

* Generates an OAuth token for authentication

31

* @returns {any} Generated token result

32

* @throws {Error} If not overridden by child class

33

*/

34

generateToken(): any;

35

36

/**

37

* Refreshes an existing OAuth token

38

* @returns {any} Refreshed token result

39

* @throws {Error} If not overridden by child class

40

*/

41

refreshToken(): any;

42

43

/**

44

* Destroys/revokes an OAuth token

45

* @returns {any} Token destruction result

46

* @throws {Error} If not overridden by child class

47

*/

48

destroyToken(): any;

49

```

50

51

## Implementation Example

52

53

```javascript

54

import { OauthService } from "medusa-interfaces";

55

import jwt from "jsonwebtoken";

56

import crypto from "crypto";

57

58

class JWTOauthService extends OauthService {

59

constructor(options) {

60

super();

61

this.secretKey = options.secret_key;

62

this.refreshTokenSecret = options.refresh_token_secret;

63

this.accessTokenExpiry = options.access_token_expiry || "15m";

64

this.refreshTokenExpiry = options.refresh_token_expiry || "7d";

65

this.tokenStorage = options.token_storage || {}; // In-memory storage for demo

66

}

67

68

async generateToken(payload) {

69

// Generate access token

70

const accessToken = jwt.sign(

71

{

72

...payload,

73

type: "access",

74

jti: crypto.randomUUID() // Unique token ID

75

},

76

this.secretKey,

77

{ expiresIn: this.accessTokenExpiry }

78

);

79

80

// Generate refresh token

81

const refreshTokenId = crypto.randomUUID();

82

const refreshToken = jwt.sign(

83

{

84

jti: refreshTokenId,

85

type: "refresh",

86

user_id: payload.user_id

87

},

88

this.refreshTokenSecret,

89

{ expiresIn: this.refreshTokenExpiry }

90

);

91

92

// Store refresh token (in production, use Redis/database)

93

this.tokenStorage[refreshTokenId] = {

94

user_id: payload.user_id,

95

created_at: new Date(),

96

is_active: true

97

};

98

99

return {

100

access_token: accessToken,

101

refresh_token: refreshToken,

102

token_type: "Bearer",

103

expires_in: this.parseExpiry(this.accessTokenExpiry),

104

scope: payload.scope || "read write"

105

};

106

}

107

108

async refreshToken(refreshTokenString) {

109

try {

110

// Verify refresh token

111

const decoded = jwt.verify(refreshTokenString, this.refreshTokenSecret);

112

113

if (decoded.type !== "refresh") {

114

throw new Error("Invalid token type");

115

}

116

117

// Check if refresh token is still active

118

const storedToken = this.tokenStorage[decoded.jti];

119

if (!storedToken || !storedToken.is_active) {

120

throw new Error("Refresh token revoked or invalid");

121

}

122

123

// Generate new access token

124

const newAccessToken = jwt.sign(

125

{

126

user_id: storedToken.user_id,

127

type: "access",

128

jti: crypto.randomUUID()

129

},

130

this.secretKey,

131

{ expiresIn: this.accessTokenExpiry }

132

);

133

134

return {

135

access_token: newAccessToken,

136

token_type: "Bearer",

137

expires_in: this.parseExpiry(this.accessTokenExpiry)

138

};

139

140

} catch (error) {

141

throw new Error(`Token refresh failed: ${error.message}`);

142

}

143

}

144

145

async destroyToken(tokenString, tokenType = "access") {

146

try {

147

let decoded;

148

149

if (tokenType === "access") {

150

decoded = jwt.verify(tokenString, this.secretKey);

151

} else if (tokenType === "refresh") {

152

decoded = jwt.verify(tokenString, this.refreshTokenSecret);

153

154

// Mark refresh token as inactive

155

if (this.tokenStorage[decoded.jti]) {

156

this.tokenStorage[decoded.jti].is_active = false;

157

}

158

}

159

160

// In production, you might maintain a blacklist of revoked tokens

161

// until their natural expiration

162

163

return {

164

success: true,

165

token_id: decoded.jti,

166

revoked_at: new Date()

167

};

168

169

} catch (error) {

170

throw new Error(`Token destruction failed: ${error.message}`);

171

}

172

}

173

174

parseExpiry(expiry) {

175

// Convert JWT expiry format to seconds

176

const unit = expiry.slice(-1);

177

const value = parseInt(expiry.slice(0, -1));

178

179

switch (unit) {

180

case "s": return value;

181

case "m": return value * 60;

182

case "h": return value * 3600;

183

case "d": return value * 86400;

184

default: return 900; // 15 minutes default

185

}

186

}

187

}

188

189

// OAuth2 Provider Implementation

190

class OAuth2Service extends OauthService {

191

constructor(options) {

192

super();

193

this.clientId = options.client_id;

194

this.clientSecret = options.client_secret;

195

this.providerUrl = options.provider_url;

196

this.redirectUri = options.redirect_uri;

197

}

198

199

async generateToken(authorizationCode) {

200

// Exchange authorization code for tokens

201

const tokenRequest = {

202

grant_type: "authorization_code",

203

code: authorizationCode,

204

client_id: this.clientId,

205

client_secret: this.clientSecret,

206

redirect_uri: this.redirectUri

207

};

208

209

try {

210

const response = await fetch(`${this.providerUrl}/oauth/token`, {

211

method: "POST",

212

headers: {

213

"Content-Type": "application/x-www-form-urlencoded"

214

},

215

body: new URLSearchParams(tokenRequest)

216

});

217

218

if (!response.ok) {

219

throw new Error(`Token exchange failed: ${response.statusText}`);

220

}

221

222

const tokenData = await response.json();

223

224

return {

225

access_token: tokenData.access_token,

226

refresh_token: tokenData.refresh_token,

227

token_type: tokenData.token_type || "Bearer",

228

expires_in: tokenData.expires_in,

229

scope: tokenData.scope

230

};

231

232

} catch (error) {

233

throw new Error(`OAuth token generation failed: ${error.message}`);

234

}

235

}

236

237

async refreshToken(refreshTokenString) {

238

const refreshRequest = {

239

grant_type: "refresh_token",

240

refresh_token: refreshTokenString,

241

client_id: this.clientId,

242

client_secret: this.clientSecret

243

};

244

245

try {

246

const response = await fetch(`${this.providerUrl}/oauth/token`, {

247

method: "POST",

248

headers: {

249

"Content-Type": "application/x-www-form-urlencoded"

250

},

251

body: new URLSearchParams(refreshRequest)

252

});

253

254

if (!response.ok) {

255

throw new Error(`Token refresh failed: ${response.statusText}`);

256

}

257

258

const tokenData = await response.json();

259

260

return {

261

access_token: tokenData.access_token,

262

refresh_token: tokenData.refresh_token || refreshTokenString,

263

token_type: tokenData.token_type || "Bearer",

264

expires_in: tokenData.expires_in,

265

scope: tokenData.scope

266

};

267

268

} catch (error) {

269

throw new Error(`OAuth token refresh failed: ${error.message}`);

270

}

271

}

272

273

async destroyToken(tokenString, tokenType = "access") {

274

const revokeRequest = {

275

token: tokenString,

276

token_type_hint: tokenType,

277

client_id: this.clientId,

278

client_secret: this.clientSecret

279

};

280

281

try {

282

const response = await fetch(`${this.providerUrl}/oauth/revoke`, {

283

method: "POST",

284

headers: {

285

"Content-Type": "application/x-www-form-urlencoded"

286

},

287

body: new URLSearchParams(revokeRequest)

288

});

289

290

// OAuth2 revoke endpoint typically returns 200 even for invalid tokens

291

return {

292

success: response.ok,

293

revoked_at: new Date()

294

};

295

296

} catch (error) {

297

throw new Error(`OAuth token revocation failed: ${error.message}`);

298

}

299

}

300

301

// Helper method to generate authorization URL

302

getAuthorizationUrl(scopes = [], state = null) {

303

const params = new URLSearchParams({

304

response_type: "code",

305

client_id: this.clientId,

306

redirect_uri: this.redirectUri,

307

scope: scopes.join(" ")

308

});

309

310

if (state) {

311

params.set("state", state);

312

}

313

314

return `${this.providerUrl}/oauth/authorize?${params.toString()}`;

315

}

316

}

317

```

318

319

## Usage in Medusa

320

321

OAuth services are typically used for:

322

323

- **Admin Authentication**: JWT tokens for admin panel access

324

- **Customer Authentication**: Customer login sessions

325

- **API Access**: Third-party integrations with OAuth providers

326

- **Service-to-Service**: Inter-service authentication

327

328

**Basic Usage Pattern:**

329

330

```javascript

331

// In a Medusa authentication service

332

class AuthService {

333

constructor({ oauthService }) {

334

this.oauthService_ = oauthService;

335

}

336

337

async login(credentials) {

338

// Validate credentials

339

const user = await this.validateCredentials(credentials);

340

341

// Generate OAuth tokens

342

const tokens = await this.oauthService_.generateToken({

343

user_id: user.id,

344

email: user.email,

345

scope: user.role

346

});

347

348

return {

349

user: user,

350

...tokens

351

};

352

}

353

354

async refreshUserToken(refreshToken) {

355

try {

356

const newTokens = await this.oauthService_.refreshToken(refreshToken);

357

return newTokens;

358

} catch (error) {

359

throw new Error("Token refresh failed - please login again");

360

}

361

}

362

363

async logout(accessToken) {

364

await this.oauthService_.destroyToken(accessToken, "access");

365

return { success: true };

366

}

367

}

368

```

369

370

## Token Validation Middleware

371

372

```javascript

373

// Express middleware for token validation

374

function createAuthMiddleware(oauthService) {

375

return async (req, res, next) => {

376

const authHeader = req.headers.authorization;

377

378

if (!authHeader || !authHeader.startsWith("Bearer ")) {

379

return res.status(401).json({ error: "Missing or invalid authorization header" });

380

}

381

382

const token = authHeader.substring(7);

383

384

try {

385

// In a real implementation, you'd verify the token

386

const decoded = jwt.verify(token, process.env.JWT_SECRET);

387

req.user = decoded;

388

next();

389

} catch (error) {

390

res.status(401).json({ error: "Invalid or expired token" });

391

}

392

};

393

}

394

```

395

396

## Error Handling

397

398

All abstract methods throw descriptive errors when not implemented:

399

400

- `"generateToken must be overridden by the child class"`

401

- `"refreshToken must be overridden by the child class"`

402

- `"destroyToken must be overridden by the child class"`

403

404

## Security Considerations

405

406

When implementing OAuth services:

407

408

- **Use strong secrets** for token signing

409

- **Implement token rotation** for refresh tokens

410

- **Set appropriate expiry times** for different token types

411

- **Maintain token blacklists** for revoked tokens

412

- **Use HTTPS** for all token exchanges

413

- **Validate token audience and issuer** claims

414

- **Implement rate limiting** for token endpoints