|
| 1 | +--- |
| 2 | +title: Protected web API - app code configuration | Azure |
| 3 | +description: Learn how to build a protected Web API and configure your application's code. |
| 4 | +services: active-directory |
| 5 | +documentationcenter: dev-center-name |
| 6 | +author: jmprieur |
| 7 | +manager: CelesteDG |
| 8 | +editor: '' |
| 9 | + |
| 10 | +ms.service: active-directory |
| 11 | +ms.subservice: develop |
| 12 | +ms.devlang: na |
| 13 | +ms.topic: conceptual |
| 14 | +ms.tgt_pltfrm: na |
| 15 | +ms.workload: identity |
| 16 | +ms.date: 05/07/2019 |
| 17 | +ms.author: jmprieur |
| 18 | +ms.custom: aaddev |
| 19 | +#Customer intent: As an application developer, I want to know how to write a protected Web API using the Microsoft identity platform for developers. |
| 20 | +ms.collection: M365-identity-device-management |
| 21 | +--- |
| 22 | + |
| 23 | +# Protected web API - adding authorization to your API. |
| 24 | + |
| 25 | +This article describes how you can add authorization to your Web API. This protection ensures that it's only called by: |
| 26 | + |
| 27 | +- applications on behalf of users with the right scopes |
| 28 | +- or by daemon apps with the right application roles. |
| 29 | + |
| 30 | +For an ASP.NET / ASP.NET Core Web API to be protected, you'll need to add the `[Authorize]` attribute on: |
| 31 | + |
| 32 | +- the Controller itself if you want all the actions of the controller to be protected |
| 33 | +- the individual controller action for your API, otherwise. |
| 34 | + |
| 35 | +```CSharp |
| 36 | + [Authorize] |
| 37 | + public class TodoListController : Controller |
| 38 | + { |
| 39 | + ... |
| 40 | + } |
| 41 | +``` |
| 42 | + |
| 43 | +But this protection isn't enough. It only guaranties that ASP.NET / ASP.NET Core will validate the token. Your API needs to verify that the token used to call your Web API was requested with the claims it expects, in particular: |
| 44 | + |
| 45 | +- the **scopes** if the API is called on behalf of a user |
| 46 | +- the **app roles** if the API can be called from a daemon app. |
| 47 | + |
| 48 | +## Verifying scopes in APIs called on behalf of users |
| 49 | + |
| 50 | +If your API is called by a client app on behalf of a user, then it needs to request a bearer token with specific scopes for the API (see [Code configuration | Bearer token](https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-protected-web-api-app-configuration#bearer-token)) |
| 51 | + |
| 52 | +```CSharp |
| 53 | +[Authorize] |
| 54 | +public class TodoListController : Controller |
| 55 | +{ |
| 56 | + /// <summary> |
| 57 | + /// The Web API will only accept tokens 1) for users, 2) having the `access_as_user` scope for |
| 58 | + /// this API |
| 59 | + /// </summary> |
| 60 | + const string scopeRequiredByAPI = "access_as_user"; |
| 61 | + |
| 62 | + // GET: api/values |
| 63 | + [HttpGet] |
| 64 | + public IEnumerable<TodoItem> Get() |
| 65 | + { |
| 66 | + VerifyUserHasAnyAcceptedScope(scopeRequiredByAPI); |
| 67 | + // Do the work and return the result |
| 68 | + ... |
| 69 | + } |
| 70 | +... |
| 71 | +} |
| 72 | +``` |
| 73 | + |
| 74 | +The `VerifyUserHasAnyAcceptedScope` method would do something like the following: |
| 75 | + |
| 76 | +- verify that there's a claims named `scope` |
| 77 | +- verify that the claim has a value containing the scope expected by the API. |
| 78 | + |
| 79 | +```CSharp |
| 80 | + /// <summary> |
| 81 | + /// When applied to an <see cref="HttpContext"/>, verifies that the user authenticated in the |
| 82 | + /// Web API has any of the accepted scopes. * |
| 83 | + /// If the authentication user does not have any of these <paramref name="acceptedScopes"/>, the |
| 84 | + /// method throws an HTTP Unauthorized with the message telling which scopes are expected in the token |
| 85 | + /// </summary> |
| 86 | + /// <param name="acceptedScopes">Scopes accepted by this API</param> |
| 87 | + /// <exception cref="HttpRequestException"/> with a <see cref="HttpResponse.StatusCode"/> set to |
| 88 | + /// <see cref="HttpStatusCode.Unauthorized"/> |
| 89 | + public static void VerifyUserHasAnyAcceptedScope(this HttpContext context, |
| 90 | + params string[] acceptedScopes) |
| 91 | + { |
| 92 | + if (acceptedScopes == null) |
| 93 | + { |
| 94 | + throw new ArgumentNullException(nameof(acceptedScopes)); |
| 95 | + } |
| 96 | + Claim scopeClaim = HttpContext?.User |
| 97 | + ?.FindFirst("http://schemas.microsoft.com/identity/claims/scope"); |
| 98 | + if (scopeClaim == null || !scopeClaim.Value.Split(' ').Intersect(acceptedScopes).Any()) |
| 99 | + { |
| 100 | + context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; |
| 101 | + string message = $"The 'scope' claim does not contain scopes '{string.Join(",", acceptedScopes)}' or was not found"; |
| 102 | + throw new HttpRequestException(message); |
| 103 | + } |
| 104 | + } |
| 105 | +``` |
| 106 | + |
| 107 | +This sample code is for ASP.NET Core. For ASP.NET just replace `HttpContext.User` by `ClaimsPrincipal.Current`, and the claim type `"http://schemas.microsoft.com/identity/claims/scope"` by `"scope"` (See also the code snippet below) |
| 108 | + |
| 109 | + |
| 110 | +## Verifying app roles in APIs called by daemon apps |
| 111 | + |
| 112 | +If you Web API is called by a [Daemon application](scenario-daemon-overview.md), then that application should require an application permission |
| 113 | +to your Web API. We've seen in [scenario-protected-web-api-app-registration.md#how-to-expose-application-permissions--app-roles-] that your API |
| 114 | +exposes such permissions (for instance as the `access_as_application` app role). You now need to have your APIs verify that the token it received contains the `roles` claims and |
| 115 | +that this claim has the value it expects. To do this, the code is similar to the code that verifies delegated permissions, except that, instead of testing for `scopes`, your controller action will test for `roles`: |
| 116 | + |
| 117 | +```CSharp |
| 118 | +[Authorize] |
| 119 | +public class TodoListController : ApiController |
| 120 | +{ |
| 121 | + public IEnumerable<TodoItem> Get() |
| 122 | + { |
| 123 | + ValidateAppRole("access_as_application"); |
| 124 | + ... |
| 125 | + } |
| 126 | +``` |
| 127 | + |
| 128 | +The ValidateAppRole() method can be something like this: |
| 129 | + |
| 130 | +```CSharp |
| 131 | + private void ValidateAppRole(string appRole) |
| 132 | + { |
| 133 | + // |
| 134 | + // The `role` claim tells you what permissions the client application has in the service. |
| 135 | + // In this case we look for a `role` value of `access_as_application` |
| 136 | + // |
| 137 | + Claim scopeClaim = ClaimsPrincipal.Current.FindFirst("roles"); |
| 138 | + if (scopeClaim == null || (scopeClaim.Value != appRole)) |
| 139 | + { |
| 140 | + throw new HttpResponseException(new HttpResponseMessage { StatusCode = HttpStatusCode.Unauthorized, ReasonPhrase = $"The 'roles' claim does not contain '{appRole}' or was not found" }); |
| 141 | + } |
| 142 | + } |
| 143 | +} |
| 144 | +``` |
| 145 | + |
| 146 | +This sample code is for ASP.NET. For ASP.NET Core, just replace `ClaimsPrincipal.Current` by `HttpContext.User` and the `"roles"` claim name by `"http://schemas.microsoft.com/identity/claims/roles"` (see also the code snippet above) |
| 147 | + |
| 148 | +## Next steps |
| 149 | + |
| 150 | +> [!div class="nextstepaction"] |
| 151 | +> [Move to production](scenario-protected-web-api-production.md) |
0 commit comments