aven 0.0.1 → 0.0.2
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.
- checksums.yaml +4 -4
- data/Rakefile +1 -1
- data/app/components/aven/views/oauth/error/component.html.erb +44 -0
- data/app/components/aven/views/oauth/error/component.rb +30 -0
- data/app/components/aven/views/static/index/component.html.erb +4 -4
- data/app/components/aven/views/static/index/component.rb +11 -0
- data/app/controllers/aven/admin/base.rb +4 -4
- data/app/controllers/aven/application_controller.rb +22 -0
- data/app/controllers/aven/auth_controller.rb +6 -58
- data/app/controllers/aven/oauth/auth0_controller.rb +84 -0
- data/app/controllers/aven/oauth/base_controller.rb +183 -0
- data/app/controllers/aven/oauth/documentation/auth0.md +387 -0
- data/app/controllers/aven/oauth/documentation/entra_id.md +608 -0
- data/app/controllers/aven/oauth/documentation/github.md +329 -0
- data/app/controllers/aven/oauth/documentation/google.md +253 -0
- data/app/controllers/aven/oauth/entra_id_controller.rb +92 -0
- data/app/controllers/aven/oauth/github_controller.rb +91 -0
- data/app/controllers/aven/oauth/google_controller.rb +64 -0
- data/app/controllers/aven/workspaces_controller.rb +20 -0
- data/app/controllers/concerns/aven/authentication.rb +49 -0
- data/app/controllers/concerns/aven/controller_helpers.rb +38 -0
- data/app/helpers/aven/application_helper.rb +2 -6
- data/app/models/aven/app_record.rb +1 -1
- data/app/models/aven/app_record_schema.rb +0 -1
- data/app/models/aven/log.rb +0 -1
- data/app/models/aven/loggable.rb +2 -3
- data/app/models/aven/user.rb +0 -23
- data/app/models/aven/workspace.rb +49 -5
- data/app/models/aven/workspace_role.rb +0 -1
- data/app/models/aven/workspace_user.rb +0 -1
- data/app/models/aven/workspace_user_role.rb +0 -1
- data/config/routes.rb +22 -7
- data/db/migrate/{20251003090752_create_aven_users.rb → 20200101000001_create_aven_users.rb} +1 -1
- data/db/migrate/{20251004182010_create_aven_workspace_users.rb → 20200101000003_create_aven_workspace_users.rb} +1 -1
- data/db/migrate/{20251004182020_create_aven_workspace_roles.rb → 20200101000004_create_aven_workspace_roles.rb} +1 -1
- data/db/migrate/{20251004182030_create_aven_workspace_user_roles.rb → 20200101000005_create_aven_workspace_user_roles.rb} +1 -1
- data/db/migrate/{20251004190000_create_aven_logs.rb → 20200101000006_create_aven_logs.rb} +2 -3
- data/db/migrate/{20251004190100_create_aven_app_record_schemas.rb → 20200101000007_create_aven_app_record_schemas.rb} +0 -1
- data/db/migrate/{20251004190110_create_aven_app_records.rb → 20200101000008_create_aven_app_records.rb} +0 -1
- data/lib/aven/configuration.rb +26 -10
- data/lib/aven/engine.rb +15 -16
- data/lib/aven/model/tenant_model.rb +91 -0
- data/lib/aven/model.rb +6 -0
- data/lib/aven/version.rb +1 -1
- metadata +42 -69
- data/config/initializers/devise.rb +0 -43
- /data/db/migrate/{20251004182000_create_aven_workspaces.rb → 20200101000002_create_aven_workspaces.rb} +0 -0
- /data/lib/tasks/{sqema_tasks.rake → aven_tasks.rake} +0 -0
|
@@ -0,0 +1,608 @@
|
|
|
1
|
+
# Microsoft Entra ID OAuth Implementation Documentation
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Microsoft Entra ID (formerly Azure Active Directory) OAuth 2.0 integration for the Aven application. This implementation supports authentication and authorization for Microsoft work/school accounts and personal Microsoft accounts.
|
|
6
|
+
|
|
7
|
+
**Implementation Date**: October 15, 2025
|
|
8
|
+
**Controller**: `app/controllers/aven/oauth/entra_id_controller.rb`
|
|
9
|
+
**Routes**: `config/routes.rb:30-32`
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Default Scopes
|
|
14
|
+
|
|
15
|
+
The implementation uses the following default scopes:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
openid email profile User.Read Contacts.Read Mail.Send Mail.Read
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Scope Breakdown
|
|
22
|
+
|
|
23
|
+
| Scope | Purpose | Admin Consent Required | Verified Source |
|
|
24
|
+
| --------------- | ------------------------------- | ---------------------- | -------------------------------------------------------------------------------------------- |
|
|
25
|
+
| `openid` | OpenID Connect authentication | No | [OAuth 2.0 Scopes](https://learn.microsoft.com/en-us/entra/identity-platform/scopes-oidc) |
|
|
26
|
+
| `email` | Access user's email address | No | [OAuth 2.0 Scopes](https://learn.microsoft.com/en-us/entra/identity-platform/scopes-oidc) |
|
|
27
|
+
| `profile` | Access user's basic profile | No | [OAuth 2.0 Scopes](https://learn.microsoft.com/en-us/entra/identity-platform/scopes-oidc) |
|
|
28
|
+
| `User.Read` | Read user's profile information | No | [Microsoft Graph Permissions](https://learn.microsoft.com/en-us/graph/permissions-reference) |
|
|
29
|
+
| `Contacts.Read` | Read user's contacts | No | [List Contacts API](https://learn.microsoft.com/en-us/graph/api/user-list-contacts) |
|
|
30
|
+
| `Mail.Send` | Send email as the user | No\* | [Send Mail API](https://learn.microsoft.com/en-us/graph/api/user-sendmail) |
|
|
31
|
+
| `Mail.Read` | Read user's email | No\* | [List Messages API](https://learn.microsoft.com/en-us/graph/api/user-list-messages) |
|
|
32
|
+
|
|
33
|
+
**Note**: "Admin Consent Required: No" means users can consent individually. However, organization admins may configure policies requiring admin pre-approval.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Admin Consent Explanation
|
|
38
|
+
|
|
39
|
+
### What is Admin Consent?
|
|
40
|
+
|
|
41
|
+
**User Consent (Default)**:
|
|
42
|
+
|
|
43
|
+
- Individual users can grant permissions when they sign in
|
|
44
|
+
- No administrator involvement needed
|
|
45
|
+
- Works for personal Microsoft accounts and smaller organizations
|
|
46
|
+
|
|
47
|
+
**Admin Consent Override**:
|
|
48
|
+
Organizations can configure policies that require administrator approval even for normally user-consentable permissions:
|
|
49
|
+
|
|
50
|
+
- **Tenant-wide settings**: Admins can disable user consent entirely
|
|
51
|
+
- **Pre-approval**: Admins can grant permissions on behalf of all users
|
|
52
|
+
- **Restricted permissions**: Certain sensitive permissions always require admin consent
|
|
53
|
+
|
|
54
|
+
### When Admin Consent is Needed
|
|
55
|
+
|
|
56
|
+
1. **Enterprise Organizations**: Many large companies disable user consent
|
|
57
|
+
2. **Sensitive Permissions**: Higher-privilege permissions always need admin approval
|
|
58
|
+
3. **Policy Requirements**: Compliance or security policies may mandate admin review
|
|
59
|
+
4. **Shared Permissions**: Accessing shared mailboxes/resources (e.g., `Mail.Read.Shared`)
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## OAuth 2.0 Flow
|
|
64
|
+
|
|
65
|
+
### 1. Authorization Request
|
|
66
|
+
|
|
67
|
+
**Endpoint**: `https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/authorize`
|
|
68
|
+
|
|
69
|
+
**Parameters**:
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
{
|
|
73
|
+
client_id: "YOUR_CLIENT_ID",
|
|
74
|
+
redirect_uri: "https://yourdomain.com/oauth/entra_id/callback",
|
|
75
|
+
response_type: "code",
|
|
76
|
+
scope: "openid email profile User.Read Contacts.Read Mail.Send Mail.Read",
|
|
77
|
+
state: "RANDOM_STATE_TOKEN",
|
|
78
|
+
response_mode: "query"
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Optional Parameters**:
|
|
83
|
+
|
|
84
|
+
- `domain_hint`: Pre-fills login with a specific domain (e.g., "contoso.com")
|
|
85
|
+
- `prompt`: Controls the prompt behavior (e.g., "select_account", "consent")
|
|
86
|
+
|
|
87
|
+
### 2. Token Exchange
|
|
88
|
+
|
|
89
|
+
**Endpoint**: `https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token`
|
|
90
|
+
|
|
91
|
+
**Parameters**:
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
{
|
|
95
|
+
client_id: "YOUR_CLIENT_ID",
|
|
96
|
+
client_secret: "YOUR_CLIENT_SECRET",
|
|
97
|
+
code: "AUTHORIZATION_CODE",
|
|
98
|
+
redirect_uri: "https://yourdomain.com/oauth/entra_id/callback",
|
|
99
|
+
grant_type: "authorization_code",
|
|
100
|
+
scope: "openid email profile User.Read Contacts.Read Mail.Send Mail.Read"
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Response**:
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"access_token": "eyJ0eXAiOiJKV1QiLCJub25jZSI6...",
|
|
109
|
+
"token_type": "Bearer",
|
|
110
|
+
"expires_in": 3599,
|
|
111
|
+
"scope": "openid email profile User.Read Contacts.Read Mail.Send Mail.Read",
|
|
112
|
+
"refresh_token": "0.AXEA...",
|
|
113
|
+
"id_token": "eyJ0eXAiOiJKV1QiLCJhbGc..."
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### 3. User Info Retrieval
|
|
118
|
+
|
|
119
|
+
**Endpoint**: `https://graph.microsoft.com/v1.0/me`
|
|
120
|
+
|
|
121
|
+
**Response**:
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{
|
|
125
|
+
"id": "48d31887-5fad-4d73-a9f5-3c356e68a038",
|
|
126
|
+
"displayName": "John Doe",
|
|
127
|
+
"mail": "john.doe@contoso.com",
|
|
128
|
+
"userPrincipalName": "john.doe@contoso.com"
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Mapping in Controller**:
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
{
|
|
136
|
+
id: response[:id] || response[:sub],
|
|
137
|
+
email: response[:mail] || response[:userPrincipalName] || response[:email],
|
|
138
|
+
name: response[:displayName] || response[:name],
|
|
139
|
+
picture: nil
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Configuration
|
|
146
|
+
|
|
147
|
+
### Application Configuration
|
|
148
|
+
|
|
149
|
+
In your Aven initializer or configuration:
|
|
150
|
+
|
|
151
|
+
```ruby
|
|
152
|
+
config.oauth_providers = {
|
|
153
|
+
entra_id: {
|
|
154
|
+
client_id: ENV['ENTRA_ID_CLIENT_ID'],
|
|
155
|
+
client_secret: ENV['ENTRA_ID_CLIENT_SECRET'],
|
|
156
|
+
tenant_id: ENV['ENTRA_ID_TENANT_ID'], # or "common" for multi-tenant
|
|
157
|
+
# Optional configurations:
|
|
158
|
+
scope: "openid email profile User.Read Contacts.Read Mail.Send Mail.Read",
|
|
159
|
+
domain_hint: "yourdomain.com", # Pre-fills login domain
|
|
160
|
+
prompt: "select_account" # Forces account selection
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Tenant ID Options
|
|
166
|
+
|
|
167
|
+
| Tenant ID | Use Case | Behavior |
|
|
168
|
+
| --------------- | ---------------------- | --------------------------------------------------- |
|
|
169
|
+
| `common` | Multi-tenant | Allows any Microsoft account (work/school/personal) |
|
|
170
|
+
| `organizations` | Work/school only | Allows only organizational accounts |
|
|
171
|
+
| `consumers` | Personal accounts only | Allows only personal Microsoft accounts |
|
|
172
|
+
| `{tenant-guid}` | Single tenant | Restricts to specific Azure AD tenant |
|
|
173
|
+
|
|
174
|
+
### Environment Variables
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
ENTRA_ID_CLIENT_ID=12345678-1234-1234-1234-123456789abc
|
|
178
|
+
ENTRA_ID_CLIENT_SECRET=your_client_secret_value
|
|
179
|
+
ENTRA_ID_TENANT_ID=common
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Azure App Registration Setup
|
|
185
|
+
|
|
186
|
+
### 1. Create App Registration
|
|
187
|
+
|
|
188
|
+
1. Go to [Azure Portal](https://portal.azure.com)
|
|
189
|
+
2. Navigate to **Azure Active Directory** > **App registrations**
|
|
190
|
+
3. Click **New registration**
|
|
191
|
+
4. Configure:
|
|
192
|
+
- **Name**: Your application name
|
|
193
|
+
- **Supported account types**:
|
|
194
|
+
- Single tenant (your organization only)
|
|
195
|
+
- Multi-tenant (any organization)
|
|
196
|
+
- Multi-tenant + personal Microsoft accounts
|
|
197
|
+
- **Redirect URI**: `https://yourdomain.com/oauth/entra_id/callback`
|
|
198
|
+
|
|
199
|
+
### 2. Configure API Permissions
|
|
200
|
+
|
|
201
|
+
1. Go to **API permissions** in your app registration
|
|
202
|
+
2. Click **Add a permission**
|
|
203
|
+
3. Select **Microsoft Graph**
|
|
204
|
+
4. Select **Delegated permissions**
|
|
205
|
+
5. Add the following permissions:
|
|
206
|
+
- `User.Read` (usually added by default)
|
|
207
|
+
- `Contacts.Read`
|
|
208
|
+
- `Mail.Send`
|
|
209
|
+
- `Mail.Read`
|
|
210
|
+
6. (Optional) Click **Grant admin consent** to pre-approve for all users
|
|
211
|
+
|
|
212
|
+
### 3. Create Client Secret
|
|
213
|
+
|
|
214
|
+
1. Go to **Certificates & secrets**
|
|
215
|
+
2. Click **New client secret**
|
|
216
|
+
3. Add a description and select expiration
|
|
217
|
+
4. **Copy the secret value immediately** (it won't be shown again)
|
|
218
|
+
5. Store in `ENTRA_ID_CLIENT_SECRET` environment variable
|
|
219
|
+
|
|
220
|
+
### 4. Note Your IDs
|
|
221
|
+
|
|
222
|
+
- **Application (client) ID**: Found on Overview page → `ENTRA_ID_CLIENT_ID`
|
|
223
|
+
- **Directory (tenant) ID**: Found on Overview page → `ENTRA_ID_TENANT_ID`
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## Microsoft Graph API Usage
|
|
228
|
+
|
|
229
|
+
After successful authentication, the `access_token` is stored in `user.access_token` and can be used to call Microsoft Graph APIs.
|
|
230
|
+
|
|
231
|
+
### Read Contacts
|
|
232
|
+
|
|
233
|
+
```ruby
|
|
234
|
+
GET https://graph.microsoft.com/v1.0/me/contacts
|
|
235
|
+
Authorization: Bearer {access_token}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**Response**:
|
|
239
|
+
|
|
240
|
+
```json
|
|
241
|
+
{
|
|
242
|
+
"value": [
|
|
243
|
+
{
|
|
244
|
+
"id": "AAMkAGI2T...",
|
|
245
|
+
"displayName": "Jane Smith",
|
|
246
|
+
"emailAddresses": [
|
|
247
|
+
{
|
|
248
|
+
"address": "jane.smith@example.com",
|
|
249
|
+
"name": "Jane Smith"
|
|
250
|
+
}
|
|
251
|
+
]
|
|
252
|
+
}
|
|
253
|
+
]
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
**Documentation**: https://learn.microsoft.com/en-us/graph/api/user-list-contacts
|
|
258
|
+
|
|
259
|
+
### Send Email
|
|
260
|
+
|
|
261
|
+
```ruby
|
|
262
|
+
POST https://graph.microsoft.com/v1.0/me/sendMail
|
|
263
|
+
Authorization: Bearer {access_token}
|
|
264
|
+
Content-Type: application/json
|
|
265
|
+
|
|
266
|
+
{
|
|
267
|
+
"message": {
|
|
268
|
+
"subject": "Hello from Aven",
|
|
269
|
+
"body": {
|
|
270
|
+
"contentType": "Text",
|
|
271
|
+
"content": "This is a test email."
|
|
272
|
+
},
|
|
273
|
+
"toRecipients": [
|
|
274
|
+
{
|
|
275
|
+
"emailAddress": {
|
|
276
|
+
"address": "recipient@example.com"
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
]
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
**Documentation**: https://learn.microsoft.com/en-us/graph/api/user-sendmail
|
|
285
|
+
|
|
286
|
+
### Read Email
|
|
287
|
+
|
|
288
|
+
```ruby
|
|
289
|
+
GET https://graph.microsoft.com/v1.0/me/messages
|
|
290
|
+
Authorization: Bearer {access_token}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
**Optional Query Parameters**:
|
|
294
|
+
|
|
295
|
+
- `$select=subject,from,receivedDateTime,bodyPreview`
|
|
296
|
+
- `$filter=isRead eq false`
|
|
297
|
+
- `$orderby=receivedDateTime desc`
|
|
298
|
+
- `$top=10`
|
|
299
|
+
|
|
300
|
+
**Response**:
|
|
301
|
+
|
|
302
|
+
```json
|
|
303
|
+
{
|
|
304
|
+
"value": [
|
|
305
|
+
{
|
|
306
|
+
"id": "AAMkAGI2T...",
|
|
307
|
+
"subject": "Meeting Tomorrow",
|
|
308
|
+
"from": {
|
|
309
|
+
"emailAddress": {
|
|
310
|
+
"name": "John Doe",
|
|
311
|
+
"address": "john@example.com"
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
"receivedDateTime": "2025-10-15T10:30:00Z",
|
|
315
|
+
"bodyPreview": "Just a reminder about our meeting..."
|
|
316
|
+
}
|
|
317
|
+
]
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
**Documentation**: https://learn.microsoft.com/en-us/graph/api/user-list-messages
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## Official Documentation References
|
|
326
|
+
|
|
327
|
+
### Core OAuth & Authentication
|
|
328
|
+
|
|
329
|
+
1. **Microsoft Entra ID OAuth 2.0 Authorization Code Flow**
|
|
330
|
+
https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-auth-code-flow
|
|
331
|
+
|
|
332
|
+
2. **Scopes and Permissions in Microsoft Identity Platform**
|
|
333
|
+
https://learn.microsoft.com/en-us/entra/identity-platform/scopes-oidc
|
|
334
|
+
|
|
335
|
+
3. **Microsoft Graph Permissions Overview**
|
|
336
|
+
https://learn.microsoft.com/en-us/graph/permissions-overview
|
|
337
|
+
|
|
338
|
+
4. **Microsoft Graph Permissions Reference (Complete List)**
|
|
339
|
+
https://learn.microsoft.com/en-us/graph/permissions-reference
|
|
340
|
+
|
|
341
|
+
### API-Specific Documentation
|
|
342
|
+
|
|
343
|
+
5. **Send Mail API**
|
|
344
|
+
https://learn.microsoft.com/en-us/graph/api/user-sendmail
|
|
345
|
+
|
|
346
|
+
6. **List Messages API**
|
|
347
|
+
https://learn.microsoft.com/en-us/graph/api/user-list-messages
|
|
348
|
+
|
|
349
|
+
7. **List Contacts API**
|
|
350
|
+
https://learn.microsoft.com/en-us/graph/api/user-list-contacts
|
|
351
|
+
|
|
352
|
+
8. **Get Contact API**
|
|
353
|
+
https://learn.microsoft.com/en-us/graph/api/contact-get
|
|
354
|
+
|
|
355
|
+
### Additional Resources
|
|
356
|
+
|
|
357
|
+
9. **Graph Permissions Explorer** (Third-party tool)
|
|
358
|
+
https://graphpermissions.merill.net/
|
|
359
|
+
|
|
360
|
+
10. **Authentication and Authorization Basics**
|
|
361
|
+
https://learn.microsoft.com/en-us/graph/auth/auth-concepts
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## Implementation Details
|
|
366
|
+
|
|
367
|
+
### Controller Structure
|
|
368
|
+
|
|
369
|
+
**File**: `app/controllers/aven/oauth/entra_id_controller.rb`
|
|
370
|
+
|
|
371
|
+
**Inherits From**: `Aven::Oauth::BaseController`
|
|
372
|
+
|
|
373
|
+
**Implemented Methods**:
|
|
374
|
+
|
|
375
|
+
- `authorization_url(state)`: Builds the Microsoft authorization URL
|
|
376
|
+
- `exchange_code_for_token(code)`: Exchanges authorization code for access token
|
|
377
|
+
- `fetch_user_info(access_token)`: Retrieves user information from Microsoft Graph
|
|
378
|
+
|
|
379
|
+
**Helper Methods**:
|
|
380
|
+
|
|
381
|
+
- `callback_url`: Generates the OAuth callback URL
|
|
382
|
+
- `oauth_config`: Retrieves Entra ID configuration
|
|
383
|
+
- `tenant_id`: Returns the configured tenant ID (default: "common")
|
|
384
|
+
- `entra_authorization_url`: Microsoft authorization endpoint
|
|
385
|
+
- `entra_token_url`: Microsoft token endpoint
|
|
386
|
+
- `entra_userinfo_url`: Microsoft Graph user info endpoint
|
|
387
|
+
|
|
388
|
+
### Routes
|
|
389
|
+
|
|
390
|
+
**File**: `config/routes.rb:30-32`
|
|
391
|
+
|
|
392
|
+
```ruby
|
|
393
|
+
# Microsoft Entra ID OAuth
|
|
394
|
+
get "entra_id", to: "entra_id#create", as: :entra_id
|
|
395
|
+
get "entra_id/callback", to: "entra_id#callback", as: :entra_id_callback
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
**Available Routes**:
|
|
399
|
+
|
|
400
|
+
- `GET /oauth/entra_id` → Initiates OAuth flow
|
|
401
|
+
- `GET /oauth/entra_id/callback` → Handles OAuth callback
|
|
402
|
+
|
|
403
|
+
**Named Routes**:
|
|
404
|
+
|
|
405
|
+
- `oauth_entra_id_path` → `/oauth/entra_id`
|
|
406
|
+
- `oauth_entra_id_callback_path` → `/oauth/entra_id/callback`
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
## Security Considerations
|
|
411
|
+
|
|
412
|
+
### State Parameter
|
|
413
|
+
|
|
414
|
+
The implementation uses a secure random state token to prevent CSRF attacks:
|
|
415
|
+
|
|
416
|
+
```ruby
|
|
417
|
+
state = SecureRandom.hex(16)
|
|
418
|
+
session[:oauth_state] = state
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
The state is validated in the callback to ensure the response matches the request.
|
|
422
|
+
|
|
423
|
+
### Token Storage
|
|
424
|
+
|
|
425
|
+
Access tokens are stored in the `Aven::User` model:
|
|
426
|
+
|
|
427
|
+
```ruby
|
|
428
|
+
user.access_token = token_data[:access_token]
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
**Security Recommendations**:
|
|
432
|
+
|
|
433
|
+
1. Encrypt the `access_token` column in the database
|
|
434
|
+
2. Implement token refresh logic (access tokens expire)
|
|
435
|
+
3. Store `refresh_token` separately for long-term access
|
|
436
|
+
4. Use HTTPS for all OAuth endpoints
|
|
437
|
+
|
|
438
|
+
### HTTPS Requirement
|
|
439
|
+
|
|
440
|
+
OAuth 2.0 requires HTTPS for security. The implementation includes SSL verification:
|
|
441
|
+
|
|
442
|
+
```ruby
|
|
443
|
+
http.use_ssl = true
|
|
444
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE if Rails.env.development?
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
**Production**: Always use proper SSL certificates.
|
|
448
|
+
|
|
449
|
+
---
|
|
450
|
+
|
|
451
|
+
## Troubleshooting
|
|
452
|
+
|
|
453
|
+
### Common Issues
|
|
454
|
+
|
|
455
|
+
#### 1. "Invalid redirect_uri"
|
|
456
|
+
|
|
457
|
+
**Cause**: Redirect URI mismatch between your app and Azure configuration.
|
|
458
|
+
|
|
459
|
+
**Solution**: Ensure the redirect URI in Azure matches exactly:
|
|
460
|
+
|
|
461
|
+
```
|
|
462
|
+
https://yourdomain.com/oauth/entra_id/callback
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
#### 2. "AADSTS65001: The user or administrator has not consented"
|
|
466
|
+
|
|
467
|
+
**Cause**: User hasn't consented to the requested permissions.
|
|
468
|
+
|
|
469
|
+
**Solution**:
|
|
470
|
+
|
|
471
|
+
- Have admin grant consent in Azure portal
|
|
472
|
+
- Or adjust scopes to user-consentable permissions only
|
|
473
|
+
|
|
474
|
+
#### 3. "ErrorAccessDenied" when calling Graph API
|
|
475
|
+
|
|
476
|
+
**Cause**: Missing permissions or token scope mismatch.
|
|
477
|
+
|
|
478
|
+
**Solution**:
|
|
479
|
+
|
|
480
|
+
1. Verify permissions are added in Azure portal
|
|
481
|
+
2. Check the scope parameter includes all needed permissions
|
|
482
|
+
3. Request a new token after adding permissions
|
|
483
|
+
|
|
484
|
+
#### 4. "Invalid client secret"
|
|
485
|
+
|
|
486
|
+
**Cause**: Client secret expired or incorrect.
|
|
487
|
+
|
|
488
|
+
**Solution**:
|
|
489
|
+
|
|
490
|
+
1. Generate a new client secret in Azure portal
|
|
491
|
+
2. Update `ENTRA_ID_CLIENT_SECRET` environment variable
|
|
492
|
+
3. Note: Secrets expire (check expiration date)
|
|
493
|
+
|
|
494
|
+
### Debug Mode
|
|
495
|
+
|
|
496
|
+
In development, detailed errors are shown:
|
|
497
|
+
|
|
498
|
+
```ruby
|
|
499
|
+
error_message = if Rails.env.production?
|
|
500
|
+
"Authentication failed. Please try again."
|
|
501
|
+
else
|
|
502
|
+
"#{e.message}"
|
|
503
|
+
end
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
Check Rails logs for full error details.
|
|
507
|
+
|
|
508
|
+
---
|
|
509
|
+
|
|
510
|
+
## Token Refresh (Future Enhancement)
|
|
511
|
+
|
|
512
|
+
Currently, the implementation stores only the access token. For long-term access, implement refresh token logic:
|
|
513
|
+
|
|
514
|
+
```ruby
|
|
515
|
+
def refresh_access_token(refresh_token)
|
|
516
|
+
params = {
|
|
517
|
+
client_id: oauth_config[:client_id],
|
|
518
|
+
client_secret: oauth_config[:client_secret],
|
|
519
|
+
refresh_token: refresh_token,
|
|
520
|
+
grant_type: "refresh_token"
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
oauth_request(URI(entra_token_url), params)
|
|
524
|
+
end
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
Store the `refresh_token` from the initial token response and use it to obtain new access tokens when they expire (typically after 1 hour).
|
|
528
|
+
|
|
529
|
+
---
|
|
530
|
+
|
|
531
|
+
## Testing
|
|
532
|
+
|
|
533
|
+
### Manual Testing
|
|
534
|
+
|
|
535
|
+
1. Navigate to: `http://localhost:3000/oauth/entra_id`
|
|
536
|
+
2. Sign in with a Microsoft account
|
|
537
|
+
3. Grant permissions when prompted
|
|
538
|
+
4. Verify redirect back to your application
|
|
539
|
+
5. Check user record has `access_token` populated
|
|
540
|
+
|
|
541
|
+
### Test Accounts
|
|
542
|
+
|
|
543
|
+
For development, create test users in your Azure AD tenant:
|
|
544
|
+
|
|
545
|
+
- Go to **Azure Active Directory** > **Users**
|
|
546
|
+
- Create test users with various permission levels
|
|
547
|
+
|
|
548
|
+
### GraphQL Explorer
|
|
549
|
+
|
|
550
|
+
Test Graph API calls using Microsoft's Graph Explorer:
|
|
551
|
+
https://developer.microsoft.com/en-us/graph/graph-explorer
|
|
552
|
+
|
|
553
|
+
---
|
|
554
|
+
|
|
555
|
+
## Additional Features to Consider
|
|
556
|
+
|
|
557
|
+
### 1. Profile Picture Support
|
|
558
|
+
|
|
559
|
+
Microsoft Graph supports retrieving user photos:
|
|
560
|
+
|
|
561
|
+
```ruby
|
|
562
|
+
GET https://graph.microsoft.com/v1.0/me/photo/$value
|
|
563
|
+
Authorization: Bearer {access_token}
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
Update `fetch_user_info` to include picture URL.
|
|
567
|
+
|
|
568
|
+
### 2. Offline Access
|
|
569
|
+
|
|
570
|
+
Add `offline_access` scope to receive refresh tokens:
|
|
571
|
+
|
|
572
|
+
```ruby
|
|
573
|
+
DEFAULT_SCOPE = "openid email profile offline_access User.Read Contacts.Read Mail.Send Mail.Read"
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
### 3. Calendar Access
|
|
577
|
+
|
|
578
|
+
Add calendar permissions:
|
|
579
|
+
|
|
580
|
+
- `Calendars.Read`: Read user's calendar
|
|
581
|
+
- `Calendars.ReadWrite`: Full calendar access
|
|
582
|
+
|
|
583
|
+
### 4. OneDrive Access
|
|
584
|
+
|
|
585
|
+
Add file storage permissions:
|
|
586
|
+
|
|
587
|
+
- `Files.Read`: Read user's files
|
|
588
|
+
- `Files.ReadWrite`: Full file access
|
|
589
|
+
|
|
590
|
+
---
|
|
591
|
+
|
|
592
|
+
## Version History
|
|
593
|
+
|
|
594
|
+
- **v1.0** (2025-10-15): Initial implementation with authentication, contacts, and email support
|
|
595
|
+
|
|
596
|
+
---
|
|
597
|
+
|
|
598
|
+
## Support & Resources
|
|
599
|
+
|
|
600
|
+
- **Microsoft Graph API Documentation**: https://learn.microsoft.com/en-us/graph/
|
|
601
|
+
- **Azure Portal**: https://portal.azure.com
|
|
602
|
+
- **Microsoft Q&A**: https://learn.microsoft.com/en-us/answers/
|
|
603
|
+
- **Stack Overflow**: Tag with `microsoft-graph` and `azure-active-directory`
|
|
604
|
+
|
|
605
|
+
---
|
|
606
|
+
|
|
607
|
+
**Last Updated**: October 15, 2025
|
|
608
|
+
**Maintained By**: Aven Development Team
|