security_1.2.1_0a064c4c/src/security-oauth-servers.adb

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
-----------------------------------------------------------------------
--  security-oauth-servers -- OAuth Server Authentication Support
--  Copyright (C) 2016, 2017, 2018 Stephane Carrez
--  Written by Stephane Carrez (Stephane.Carrez@gmail.com)
--
--  Licensed under the Apache License, Version 2.0 (the "License");
--  you may not use this file except in compliance with the License.
--  You may obtain a copy of the License at
--
--      http://www.apache.org/licenses/LICENSE-2.0
--
--  Unless required by applicable law or agreed to in writing, software
--  distributed under the License is distributed on an "AS IS" BASIS,
--  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
--  See the License for the specific language governing permissions and
--  limitations under the License.
-----------------------------------------------------------------------
with Ada.Calendar.Conversions;
with Interfaces.C;

with Util.Log.Loggers;
with Util.Encoders.Base64;
with Util.Encoders.SHA256;
with Util.Encoders.HMAC.SHA256;

package body Security.OAuth.Servers is

   use type Ada.Calendar.Time;

   Log : constant Util.Log.Loggers.Logger := Util.Log.Loggers.Create ("Security.OAuth.Servers");

   --  ------------------------------
   --  Check if the application has the given permission.
   --  ------------------------------
   function Has_Permission (App        : in Application;
                            Permission : in Permissions.Permission_Index) return Boolean is
   begin
      return Security.Permissions.Has_Permission (App.Permissions, Permission);
   end Has_Permission;

   protected body Token_Cache is

      procedure Authenticate (Token : in String;
                              Grant : in out Grant_Type) is
         Pos : Cache_Map.Cursor := Entries.Find (Token);
      begin
         if Cache_Map.Has_Element (Pos) then
            if Grant.Expires < Ada.Calendar.Clock then
               Entries.Delete (Pos);
               Grant.Status := Expired_Grant;
            else
               Grant.Auth    := Cache_Map.Element (Pos).Auth;
               Grant.Expires := Cache_Map.Element (Pos).Expire;
               Grant.Status  := Valid_Grant;
            end if;
         end if;
      end Authenticate;

      procedure Insert (Token     : in String;
                        Expire    : in Ada.Calendar.Time;
                        Principal : in Principal_Access) is
      begin
         Entries.Insert (Token, Cache_Entry '(Expire, Principal));
      end Insert;

      procedure Remove (Token : in String) is
      begin
         Entries.Delete (Token);
      end Remove;

      procedure Timeout is
      begin
         null;
      end Timeout;

   end Token_Cache;

   --  ------------------------------
   --  Set the auth private key.
   --  ------------------------------
   procedure Set_Private_Key (Manager : in out Auth_Manager;
                              Key     : in String;
                              Decode  : in Boolean := False) is
   begin
      if Decode then
         declare
            Decoder : constant Util.Encoders.Decoder
              := Util.Encoders.Create (Util.Encoders.BASE_64_URL);
            Content : constant String := Decoder.Decode (Key);
         begin
            Manager.Private_Key := To_Unbounded_String (Content);
         end;
      else
         Manager.Private_Key := To_Unbounded_String (Key);
      end if;
   end Set_Private_Key;

   --  ------------------------------
   --  Set the application manager to use and and applications.
   --  ------------------------------
   procedure Set_Application_Manager (Manager    : in out Auth_Manager;
                                      Repository : in Application_Manager_Access) is
   begin
      Manager.Repository := Repository;
   end Set_Application_Manager;

   --  ------------------------------
   --  Set the realm manager to authentify users.
   --  ------------------------------
   procedure Set_Realm_Manager (Manager : in out Auth_Manager;
                                Realm   : in Realm_Manager_Access) is
   begin
      Manager.Realm := Realm;
   end Set_Realm_Manager;

   --  ------------------------------
   --  Authorize the access to the protected resource by the application and for the
   --  given principal.  The resource owner has been verified and is represented by the
   --  <tt>Auth</tt> principal.  Extract from the request parameters represented by
   --  <tt>Params</tt> the application client id, the scope and the expected response type.
   --  Handle the "Authorization Code Grant" and "Implicit Grant" defined in RFC 6749.
   --  ------------------------------
   procedure Authorize (Realm   : in out Auth_Manager;
                        Params  : in Security.Auth.Parameters'Class;
                        Auth    : in Principal_Access;
                        Grant   : out Grant_Type) is
      Method    : constant String := Params.Get_Parameter (Security.OAuth.RESPONSE_TYPE);
      Client_Id : constant String := Params.Get_Parameter (Security.OAuth.CLIENT_ID);
   begin
      if Client_Id'Length = 0 then
         Grant.Status := Invalid_Grant;
         Grant.Error  := INVALID_REQUEST'Access;
         return;
      end if;
      declare
         App : constant Application'Class := Realm.Repository.Find_Application (Client_Id);
      begin
         if Method = "code" then
            Realm.Authorize_Code (App, Params, Auth, Grant);
         elsif Method = "token" then
            Realm.Authorize_Token (App, Params, Auth, Grant);
         else
            Grant.Status := Invalid_Grant;
            Grant.Error := UNSUPPORTED_RESPONSE_TYPE'Access;
            Log.Warn ("Authorize method '{0}' is not supported", Method);
         end if;
      end;

   exception
      when Invalid_Application =>
         Log.Warn ("Invalid client_id {0}", Client_Id);
         Grant.Status := Invalid_Grant;
         Grant.Error  := INVALID_CLIENT'Access;
         return;

      when E : others =>
         Log.Error ("Error while doing authorization for client_id " & Client_Id, E);
         Grant.Status := Invalid_Grant;
         Grant.Error  := SERVER_ERROR'Access;

   end Authorize;

   --  ------------------------------
   --  The <tt>Token</tt> procedure is the main entry point to get the access token and
   --  refresh token.  The request parameters are accessed through the <tt>Params</tt> interface.
   --  The operation looks at the "grant_type" parameter to identify the access method.
   --  It also looks at the "client_id" to find the application for which the access token
   --  is created.  Upon successful authentication, the operation returns a grant.
   --  ------------------------------
   procedure Token (Realm   : in out Auth_Manager;
                    Params  : in Security.Auth.Parameters'Class;
                    Grant   : out Grant_Type) is
      Method    : constant String := Params.Get_Parameter (Security.OAuth.GRANT_TYPE);
      Client_Id : constant String := Params.Get_Parameter (Security.OAuth.CLIENT_ID);
   begin
      if Length (Realm.Private_Key) < MIN_KEY_LENGTH then
         Log.Error ("The private key is too short to generate a secure token");
         Grant.Status := Invalid_Grant;
         Grant.Error := SERVER_ERROR'Access;
         return;
      end if;
      if Client_Id'Length = 0 then
         Grant.Status := Invalid_Grant;
         Grant.Error := INVALID_REQUEST'Access;
         return;
      end if;
      declare
         App : constant Application'Class := Realm.Repository.Find_Application (Client_Id);
      begin
         if Method = "authorization_code" then
            Realm.Token_From_Code (App, Params, Grant);
         elsif Method = "password" then
            Realm.Token_From_Password (App, Params, Grant);
         elsif Method = "refresh_token" then
            Grant.Error := UNSUPPORTED_GRANT_TYPE'Access;
         elsif Method = "client_credentials" then
            Grant.Error := UNSUPPORTED_GRANT_TYPE'Access;
         else
            Grant.Error := UNSUPPORTED_GRANT_TYPE'Access;
            Log.Warn ("Grant type '{0}' is not supported", Method);
         end if;
      end;

   exception
      when Invalid_Application =>
         Log.Warn ("Invalid client_id '{0}'", Client_Id);
         Grant.Status := Invalid_Grant;
         Grant.Error  := INVALID_CLIENT'Access;
         return;
   end Token;

   --  ------------------------------
   --  Format the expiration date to a compact string.  The date is transformed to a Unix
   --  date and encoded in LEB128 + base64url.
   --  ------------------------------
   function Format_Expire (Expire : in Ada.Calendar.Time) return String is
      T : constant Interfaces.C.long := Ada.Calendar.Conversions.To_Unix_Time (Expire);
   begin
      return Util.Encoders.Base64.Encode (Interfaces.Unsigned_64 (T));
   end Format_Expire;

   --  ------------------------------
   --  Decode the expiration date that was extracted from the token.
   --  ------------------------------
   function Parse_Expire (Expire : in String) return Ada.Calendar.Time is
      V : constant Interfaces.Unsigned_64 := Util.Encoders.Base64.Decode (Expire);
   begin
      return Ada.Calendar.Conversions.To_Ada_Time (Interfaces.C.long (V));
   end Parse_Expire;

   --  Implement the RFC 6749: 4.1.1.  Authorization Request for the authorization code grant.
   procedure Authorize_Code (Realm   : in out Auth_Manager;
                             App     : in Application'Class;
                             Params  : in Security.Auth.Parameters'Class;
                             Auth    : in Principal_Access;
                             Grant   : out Grant_Type) is
      Callback  : constant String := Params.Get_Parameter (Security.OAuth.REDIRECT_URI);
      Scope     : constant String := Params.Get_Parameter (Security.OAuth.SCOPE);
   begin
      Grant.Request := Code_Grant;
      Grant.Status  := Invalid_Grant;
      if Auth = null then
         Log.Info ("Authorization is denied");
         Grant.Error := ACCESS_DENIED'Access;

      elsif App.Callback /= Callback then
         Log.Info ("Invalid application callback");
         Grant.Error := UNAUTHORIZED_CLIENT'Access;

      else
         --  Manager'Class (Realm).Authorize (Auth, Scope);
         Grant.Expires := Ada.Calendar.Clock + Realm.Expire_Code;
         Grant.Expires_In := Realm.Expire_Code;
         Grant.Status  := Valid_Grant;
         Grant.Auth    := Auth;
         Realm.Create_Token (Realm.Realm.Authorize (App, Scope, Auth), Grant);
      end if;
   end Authorize_Code;

   --  Implement the RFC 6749: 4.2.1.  Authorization Request for the implicit grant.
   procedure Authorize_Token (Realm   : in out Auth_Manager;
                              App     : in Application'Class;
                              Params  : in Security.Auth.Parameters'Class;
                              Auth    : in Principal_Access;
                              Grant   : out Grant_Type) is
      Callback  : constant String := Params.Get_Parameter (Security.OAuth.REDIRECT_URI);
      Scope     : constant String := Params.Get_Parameter (Security.OAuth.SCOPE);
   begin
      Grant.Request := Implicit_Grant;
      Grant.Status  := Invalid_Grant;
      if Auth = null then
         Log.Info ("Authorization is denied");
         Grant.Error := ACCESS_DENIED'Access;

      elsif App.Callback /= Callback then
         Log.Info ("Invalid application callback");
         Grant.Error := UNAUTHORIZED_CLIENT'Access;

      else
         Grant.Expires := Ada.Calendar.Clock + App.Expire_Timeout;
         Grant.Expires_In := App.Expire_Timeout;
         Grant.Status  := Valid_Grant;
         Grant.Auth    := Auth;
         Realm.Create_Token (Realm.Realm.Authorize (App, Scope, Grant.Auth), Grant);
      end if;
   end Authorize_Token;

   --  Make the access token from the authorization code that was created by the
   --  <tt>Authorize</tt> operation.  Verify the client application, the redirect uri, the
   --  client secret and the validity of the authorization code.  Extract from the
   --  authorization code the auth principal that was used for the grant and make the
   --  access token.
   procedure Token_From_Code (Realm   : in out Auth_Manager;
                              App     : in Application'Class;
                              Params  : in Security.Auth.Parameters'Class;
                              Grant   : out Grant_Type) is
      Code      : constant String := Params.Get_Parameter (Security.OAuth.CODE);
      Callback  : constant String := Params.Get_Parameter (Security.OAuth.REDIRECT_URI);
      Secret    : constant String := Params.Get_Parameter (Security.OAuth.CLIENT_SECRET);
      Token     : Token_Validity;
   begin
      Grant.Request := Code_Grant;
      Grant.Status  := Invalid_Grant;
      if Code'Length = 0 then
         Log.Info ("Missing authorization code request parameter");
         Grant.Error := INVALID_REQUEST'Access;

      elsif App.Secret /= Secret then
         Log.Info ("Invalid application secret");
         Grant.Error := UNAUTHORIZED_CLIENT'Access;

      elsif App.Callback /= Callback then
         Log.Info ("Invalid application callback");
         Grant.Error := UNAUTHORIZED_CLIENT'Access;

      else
         Token := Realm.Validate (To_String (App.Client_Id), Code);
         Grant.Status := Token.Status;
         if Token.Status /= Valid_Grant then
            Log.Info ("Invalid authorization code {0}", Code);
            Grant.Error := ACCESS_DENIED'Access;
         else
            --  Verify the identification token and get the principal.
            Realm.Realm.Verify (Code (Token.Ident_Start .. Token.Ident_End), Grant.Auth);
            if Grant.Auth = null then
               Log.Info ("Access denied for authorization code {0}", Code);
               Grant.Error := ACCESS_DENIED'Access;
            else
               --  Extract user/session ident from code.
               Grant.Expires := Ada.Calendar.Clock + App.Expire_Timeout;
               Grant.Expires_In := App.Expire_Timeout;
               Grant.Error   := null;
               Realm.Create_Token (Realm.Realm.Authorize (App, SCOPE, Grant.Auth), Grant);
            end if;
         end if;
      end if;
   end Token_From_Code;

   --  ------------------------------
   --  Make the access token from the resource owner password credentials.  The username,
   --  password and scope are extracted from the request and they are verified through the
   --  <tt>Verify</tt> procedure to obtain an associated principal.  When successful, the
   --  principal describes the authorization and it is used to forge the access token.
   --  This operation implements the RFC 6749: 4.3.  Resource Owner Password Credentials Grant.
   --  ------------------------------
   procedure Token_From_Password (Realm   : in out Auth_Manager;
                                  App     : in Application'Class;
                                  Params  : in Security.Auth.Parameters'Class;
                                  Grant   : out Grant_Type) is
      Username  : constant String := Params.Get_Parameter (Security.OAuth.USERNAME);
      Password  : constant String := Params.Get_Parameter (Security.OAuth.PASSWORD);
      Scope     : constant String := Params.Get_Parameter (Security.OAuth.SCOPE);
      Secret    : constant String := Params.Get_Parameter (Security.OAuth.CLIENT_SECRET);
   begin
      Grant.Request := Password_Grant;
      Grant.Status  := Invalid_Grant;
      if Username'Length = 0 then
         Log.Info ("Missing username request parameter");
         Grant.Error := INVALID_REQUEST'Access;

      elsif Password'Length = 0 then
         Log.Info ("Missing password request parameter");
         Grant.Error := INVALID_REQUEST'Access;

      elsif App.Secret /= Secret then
         Log.Info ("Invalid application secret");
         Grant.Error := UNAUTHORIZED_CLIENT'Access;

      else
         --  Verify the username and password to get the principal.
         Realm.Realm.Verify (Username, Password, Grant.Auth);
         if Grant.Auth = null then
            Log.Info ("Access denied for {0}", Username);
            Grant.Error := ACCESS_DENIED'Access;
         else
            Grant.Status  := Valid_Grant;
            Grant.Expires := Ada.Calendar.Clock + App.Expire_Timeout;
            Grant.Expires_In := App.Expire_Timeout;
            Grant.Error   := null;
            Realm.Create_Token (Realm.Realm.Authorize (App, Scope, Grant.Auth), Grant);
         end if;
      end if;
   end Token_From_Password;

   --  ------------------------------
   --  Create a HMAC-SHA1 of the data with the private key.
   --  This function can be overriden to use another signature algorithm.
   --  ------------------------------
   function Sign (Realm : in Auth_Manager;
                  Data  : in String) return String is
      Ctx    : Util.Encoders.HMAC.SHA256.Context;
      Result : Util.Encoders.SHA256.Base64_Digest;
   begin
      Util.Encoders.HMAC.SHA256.Set_Key (Ctx, To_String (Realm.Private_Key));
      Util.Encoders.HMAC.SHA256.Update (Ctx, Data);
      Util.Encoders.HMAC.SHA256.Finish_Base64 (Ctx, Result, True);
      return Result;
   end Sign;

   --  ------------------------------
   --  Forge an access token.  The access token is signed by an HMAC-SHA256 signature.
   --  The returned token is formed as follows:
   --    <expiration>.<ident>.HMAC-SHA256(<private-key>, <expiration>.<ident>)
   --  See also RFC 6749: 5.  Issuing an Access Token
   --  ------------------------------
   procedure Create_Token (Realm  : in Auth_Manager;
                           Ident  : in String;
                           Grant  : in out Grant_Type) is
      Exp   : constant String := Format_Expire (Grant.Expires);
      Data  : constant String := Exp & "." & Ident;
      Hmac  : constant String := Auth_Manager'Class (Realm).Sign (Data);
   begin
      Grant.Token := Ada.Strings.Unbounded.To_Unbounded_String (Data & "." & Hmac);
   end Create_Token;

   --  Validate the token by checking that it is well formed, it has not expired
   --  and the HMAC-SHA256 signature is valid.  Return the set of information to allow
   --  the extraction of the auth identification from the token public part.
   function Validate (Realm     : in Auth_Manager;
                      Client_Id : in String;
                      Token     : in String) return Token_Validity is
      Pos1   : constant Natural := Util.Strings.Index (Token, '.');
      Pos2   : constant Natural := Util.Strings.Rindex (Token, '.');
      Result : Token_Validity := (Status => Invalid_Grant, others => <>);
   begin
      --  Verify the access token validity.
      if Pos1 = 0 or Pos2 = 0 or Pos1 = Pos2 then
         Log.Info ("Authenticate bad formed access token {0}", Token);
         return Result;
      end if;

      --  Build the HMAC signature with the private key.
      declare
         Hmac : constant String
            := Auth_Manager'Class (Realm).Sign (Token (Token'First .. Pos2 - 1));
      begin
         --  Check the HMAC signature part.
         if Token (Pos2 + 1 .. Token'Last) /= Hmac then
            Log.Info ("Bad signature for access token {0}", Token);
            return Result;
         end if;

         --  Signature is valid we can check the token expiration date.
         Result.Expire := Parse_Expire (Token (Token'First .. Pos1 - 1));
         if Result.Expire < Ada.Calendar.Clock then
            Log.Info ("Token {0} has expired", Token);
            Result.Status := Expired_Grant;
            return Result;
         end if;

         Result.Ident_Start := Pos1 + 1;
         Result.Ident_End   := Pos2 - 1;

         --  When an identifier is passed, verify it.
         if Client_Id'Length > 0 then
            Result.Ident_Start := Util.Strings.Index (Token, '.', Pos1 + 1);
            if Result.Ident_Start = 0
              or else Client_Id /= Token (Pos1 + 1 .. Result.Ident_Start - 1)
            then
               Log.Info ("Token {0} was stealed for another application", Token);
               Result.Status := Stealed_Grant;
               return Result;
            end if;
         end if;

         --  The access token is valid.
         Result.Status := Valid_Grant;
         return Result;
      end;

   exception
      when E : others =>
         --  No exception should ever be raised because we verify the signature first.
         Log.Error ("Token " & Token & " raised an exception", E);
         Result.Status := Invalid_Grant;
         return Result;

   end Validate;

   --  ------------------------------
   --  Authenticate the access token and get a security principal that identifies the app/user.
   --  See RFC 6749, 7.  Accessing Protected Resources.
   --  The access token is first searched in the cache.  If it was found, it means the access
   --  token was already verified in the past, it is granted and associated with a principal.
   --  Otherwise, we have to verify the token signature first, then the expiration date and
   --  we extract from the token public part the auth identification.  The <tt>Authenticate</tt>
   --  operation is then called to obtain the principal from the auth identification.
   --  When access token is invalid or authentification cannot be verified, a null principal
   --  is returned.  The <tt>Grant</tt> data will hold the result of the grant with the reason
   --  of failures (if any).
   --  ------------------------------
   procedure Authenticate (Realm  : in out Auth_Manager;
                           Token  : in String;
                           Grant  : out Grant_Type) is
      Cacheable : Boolean;
      Check     : Token_Validity;
   begin
      Check := Realm.Validate ("", Token);
      Grant.Status  := Check.Status;
      Grant.Request := Access_Grant;
      Grant.Expires := Check.Expire;
      Grant.Auth    := null;
      if Check.Status = Expired_Grant then
         Log.Info ("Access token {0} has expired", Token);

      elsif Check.Status /= Valid_Grant then
         Log.Info ("Access token {0} is invalid", Token);

      else
         Realm.Cache.Authenticate (Token, Grant);
         if Grant.Auth /= null then
            Log.Debug ("Authenticate access token {0} succeeded from cache", Token);
            return;
         end if;

         --  The access token is valid, well formed and has not expired.
         --  Get the associated principal (the only possibility it could fail is
         --  that it was revoked).
         Realm.Realm.Authenticate (Token (Check.Ident_Start .. Check.Ident_End),
                                   Grant.Auth, Cacheable);
         if Grant.Auth = null then
            Log.Info ("Access token {0} was revoked", Token);
            Grant.Status := Revoked_Grant;

            --  We are allowed to keep the token in the cache, insert it.
         elsif Cacheable then
            Realm.Cache.Insert (Token, Check.Expire, Grant.Auth);
            Log.Debug ("Access token {0} is granted and inserted in the cache", Token);

         else
            Log.Debug ("Access token {0} is granted", Token);
         end if;
      end if;
   end Authenticate;

   procedure Revoke (Realm     : in out Auth_Manager;
                     Token     : in String) is
      Check     : Token_Validity;
      Auth      : Principal_Access;
      Cacheable : Boolean;
   begin
      Check := Realm.Validate ("", Token);
      if Check.Status = Valid_Grant then

         --  The access token is valid, well formed and has not expired.
         --  Get the associated principal (the only possibility it could fail is
         --  that it was revoked).
         Realm.Realm.Authenticate (Token (Check.Ident_Start .. Check.Ident_End),
                                   Auth, Cacheable);
         if Auth /= null then
            Realm.Cache.Remove (Token);
            Realm.Realm.Revoke (Auth);
         end if;
      end if;
   end Revoke;

end Security.OAuth.Servers;