206mph
206mph

Reputation: 439

IdentityServer4 API Key Support

I have 3 projects in Visual Studio.

  1. Identity Server
  2. API Site
  3. Angular 2 front end

I'm using IdentityServer4 with ASP.NET Core Identity and Entity Framework. Everything is working great so far.

Now, I want to add the ability for users to generate an API key so the user can make calls to our API server via their server and simply pass in the API Key. That will then (somehow) authenticate the user and generate an Access Token used by my API for authorization. Does IdentityServer4 support generating API keys for each user? I know there are client secrets, which I have implemented but that only identifies the client. I need to know the user too so I know what the user is authorized to do (using roles right now for that).

Upvotes: 4

Views: 9434

Answers (1)

Marcus
Marcus

Reputation: 453

IdentityServer is a framework and a hostable component that allows implementing single sign-on and access control for modern web applications and APIs using protocols like OpenID Connect and OAuth2. It supports a wide range of clients like mobile, web, SPAs and desktop applications and is extensible to allow integration in new and existing architectures. in your startup class:

     public void ConfigureServices(IServiceCollection services)
                {
                    var source = System.IO.File.ReadAllText("MyCertificate.b64cert");
                    var certBytes = Convert.FromBase64String(source);
                    var certificate = new X509Certificate2(certBytes, "password");

                    var builder = services.AddIdentityServer(options =>
                    {
                        options.SigningCertificate = certificate;
                        options.RequireSsl = false; // should be true
                    });
             JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
        app.UseIdentityServerAuthentication(options =>
        {
            options.Authority = "http://localhost:5000";
            options.ScopeName = "openid";
            options.AutomaticAuthenticate = true;
            options.AutomaticChallenge = true;
        });
                    builder.AddInMemoryClients(Clients.Get());
                    builder.AddInMemoryScopes(Scopes.Get());
                    builder.AddInMemoryUsers(Users.Get());
                }

            public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
                    {
                        loggerFactory.AddConsole(LogLevel.Verbose);
                        loggerFactory.AddDebug(LogLevel.Verbose);
                        app.UseIdentityServer();
                    }

create a Clients class

public class Clients
{
    public static IEnumerable<Client> Get()
    {
        return new[]
        {
            new Client
            {
                ClientId = "myapi",
                ClientSecrets = new List<Secret>
                {
                    new Secret("secret".Sha256())
                },
                ClientName = "your api",
                Flow = Flows.ResourceOwner,
                AllowedScopes =
                {
                    Constants.StandardScopes.OpenId,
                    "read"
                },
                Enabled = true
            }
        };
    }
}

scopes

public class Scopes
{
    public static IEnumerable<Scope> Get()
    {
        return new[]
        {
            StandardScopes.OpenId,
            StandardScopes.Profile,
            StandardScopes.OfflineAccess,
            new Scope {Name = "advanced", DisplayName = "Advanced Options"}
        };
    }
}

controller

[Route("api/[controller]")]
public class SomeController : Controller
{
    [HttpGet]
    [Authorize]
    public IEnumerable<string> Get()
    {
        return new[] { "value1", "value2" };
    }

    [HttpGet("{id}")]
    [Authorize]
    public string Get(int id)
    {
        return "value";
    }
}

once this is setup, you can issue the JWT authentication token that the user can then use in their Authorization header

create a generating class

private async Task GenerateToken(HttpContext context)
{
    var username = context.Request.Form["username"];
    var password = context.Request.Form["password"];

    var identity = await GetIdentity(username, password);
    if (identity == null)
    {
        context.Response.StatusCode = 400;
        await context.Response.WriteAsync("Invalid username or password.");
        return;
    }

    var now = DateTime.UtcNow;

    // Specifically add the jti (random nonce), iat (issued timestamp), and sub (subject/user) claims.
    // You can add other claims here, if you want:
    var claims = new Claim[]
    {
        new Claim(JwtRegisteredClaimNames.Sub, username),
        new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
        new Claim(JwtRegisteredClaimNames.Iat, now.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64)
    };

    // Create the JWT and write it to a string
    var jwt = new JwtSecurityToken(
        issuer: _options.Issuer,
        audience: _options.Audience,
        claims: claims,
        notBefore: now,
        expires: now.Add(_options.Expiration),
        signingCredentials: _options.SigningCredentials);
    var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);

    var response = new
    {
        access_token = encodedJwt,
        expires_in = (int)_options.Expiration.TotalSeconds
    };

    // Serialize and return the response
    context.Response.ContentType = "application/json";
    await context.Response.WriteAsync(JsonConvert.SerializeObject(response, new JsonSerializerSettings { Formatting = Formatting.Indented }));
}

create your middleware:

using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;

namespace SimpleTokenProvider
{
    public class TokenProviderMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly TokenProviderOptions _options;

        public TokenProviderMiddleware(
            RequestDelegate next,
            IOptions<TokenProviderOptions> options)
        {
            _next = next;
            _options = options.Value;
        }

        public Task Invoke(HttpContext context)
        {
            // If the request path doesn't match, skip
            if (!context.Request.Path.Equals(_options.Path, StringComparison.Ordinal))
            {
                return _next(context);
            }

            // Request must be POST with Content-Type: application/x-www-form-urlencoded
            if (!context.Request.Method.Equals("POST")
               || !context.Request.HasFormContentType)
            {
                context.Response.StatusCode = 400;
                return context.Response.WriteAsync("Bad request.");
            }

            return GenerateToken(context);
        }
    }
}

and lastly wireup your middleware for generating the tokens in your startup.cs

app.UseMiddleware<TokenProviderMiddleware>(Options.Create(options));

Upvotes: 7

Related Questions