12
votes

I am implementing IdentityServer4 an I am making 3 diferents proyects:

All the project are created with ASP.NET Core, but the JS Client use static files.

I need that the JS Client connect with the API only with identity token (not access token) because I only need to have access to the API, I dont need to manage user autentication.

I am reading the quickstarts post https://identityserver4.readthedocs.io/en/dev/quickstarts/1_client_credentials.html

As I read I consider that I need to user the Implicit Grand Type and I dont need OpenID Connect, only OAuth2.

Also I read this post https://identityserver4.readthedocs.io/en/dev/quickstarts/7_javascript_client.html But they use access token and I dont need that, to connect to the API I am using oidc-client-js library https://github.com/IdentityModel/oidc-client-js and I search the way to use with the Implicit Grand Type but the methods that I use redirect me to a http://localhost:5000/connect/authorize page (I think this is when I need to use OpenID Connect)

What is the best way to achieve that? What I have wrong? How can I autenticate with the api and call http://localhost:5001/values

IdentityServer Project

Config.cs

public static IEnumerable<Client> GetClients()
        {
            return new List<Client>
            {
                new Client
                {
                    ClientId = "client",
                    ClientName = "JavaScript Client",
                    // no interactive user, use the clientid/secret for authentication
                    AllowedGrantTypes = GrantTypes.Implicit,
                    AllowAccessTokensViaBrowser = true,



                    RedirectUris = new List<string>
                    {
                        "http://localhost:5003/oidc-client-sample-callback.html"
                    },
                    AllowedCorsOrigins = new List<string>
                    {
                        "http://localhost:5003"
                    },

                    // scopes that client has access to
                    AllowedScopes = new List<string>
                    {
                        "api1"
                    }
                }
            };
        }

Startup.cs

    public void ConfigureServices(IServiceCollection services)
    {
        // configure identity server with in-memory stores, keys, clients and scopes
        services.AddDeveloperIdentityServer()
            .AddInMemoryScopes(Config.GetScopes())
            .AddInMemoryClients(Config.GetClients());
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole(LogLevel.Debug);
        app.UseDeveloperExceptionPage();

        app.UseIdentityServer();
    }

API project

Startup.cs

public void ConfigureServices(IServiceCollection services)
{

    // Add framework services.
    services.AddMvc();

    services.AddSingleton<ITodoRepository, TodoRepository>();

    services.AddCors(options =>
    {
        // this defines a CORS policy called "default"
        options.AddPolicy("default", policy =>
        {
            policy.WithOrigins("http://localhost:5003")
                .AllowAnyHeader()
                .AllowAnyMethod();
        });
    });

    services.AddMvcCore()
        .AddAuthorization()
        .AddJsonFormatters();


}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    app.UseCors("default");

    app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
    {
        Authority = "http://localhost:5000",
        ScopeName = "api1",

        RequireHttpsMetadata = false
    });

    app.UseMvc();

}

ValuesController.cs

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

        // GET api/values/5
        [HttpGet("{id}")]
        public string Get(int id)
        {
            return "value";
        }
}

Javascript client project

oidc-client-sample.html

<!DOCTYPE html>
<html>
<head>
    <title>oidc-client test</title>
    <link rel='stylesheet' href='app.css'>
</head>
<body>
    <div>
        <a href='/'>home</a>
        <a href='oidc-client-sample.html'>clear url</a>
        <label>
            follow links
            <input type="checkbox" id='links'>
        </label>
        <button id='signin'>signin</button>
        <button id='processSignin'>process signin response</button>
        <button id='signinDifferentCallback'>signin using different callback page</button>
        <button id='signout'>signout</button>
        <button id='processSignout'>process signout response</button>
    </div>

    <pre id='out'></pre>

    <script src='oidc-client.js'></script>
    <script src='log.js'></script>
    <script src='oidc-client-sample.js'></script>
</body>
</html>

oidc-client-sample.js

///////////////////////////////
// UI event handlers
///////////////////////////////
document.getElementById('signin').addEventListener("click", signin, false);
document.getElementById('processSignin').addEventListener("click", processSigninResponse, false);
document.getElementById('signinDifferentCallback').addEventListener("click", signinDifferentCallback, false);
document.getElementById('signout').addEventListener("click", signout, false);
document.getElementById('processSignout').addEventListener("click", processSignoutResponse, false);
document.getElementById('links').addEventListener('change', toggleLinks, false);

///////////////////////////////
// OidcClient config
///////////////////////////////
Oidc.Log.logger = console;
Oidc.Log.level = Oidc.Log.INFO;

var settings = {
    authority: 'http://localhost:5000/',
    client_id: 'client',
    redirect_uri: 'http://localhost:5003/oidc-client-sample-callback.html',
    response_type: 'token',
    scope: 'api1'
};
var client = new Oidc.OidcClient(settings);

///////////////////////////////
// functions for UI elements
///////////////////////////////
function signin() {
    client.createSigninRequest({ data: { bar: 15 } }).then(function (req) {
        log("signin request", req, "<a href='" + req.url + "'>go signin</a>");
        if (followLinks()) {
            window.location = req.url;
        }
    }).catch(function (err) {
        log(err);
    });
}
function api() {
    client.getUser().then(function (user) {
        var url = "http://localhost:5001/values";

        var xhr = new XMLHttpRequest();
        xhr.open("GET", url);
        xhr.onload = function () {
            log(xhr.status, JSON.parse(xhr.responseText));
        }
        xhr.setRequestHeader("Authorization", "Bearer " + user.access_token);
        xhr.send();
    });
}

oidc-client-sample-callback.html

<!DOCTYPE html>
<html>
<head>
    <title>oidc-client test</title>
    <link rel='stylesheet' href='app.css'>
</head>
<body>
    <div>
        <a href="oidc-client-sample.html">back to sample</a>
    </div>
    <pre id='out'></pre>
    <script src='log.js'></script>
    <script src='oidc-client.js'></script>
    <script>
            Oidc.Log.logger = console;
            Oidc.Log.logLevel = Oidc.Log.INFO;
            new Oidc.OidcClient().processSigninResponse().then(function(response) {
                log("signin response success", response);
            }).catch(function(err) {
                log(err);
            });
    </script>
</body>
</html>
1
Why not make the API anonymous and save a bunch of work? You'll have to hardcode the Client ID + Client Secret in JavaScript, which means they're compromised, there's no way to keep a secret in JavaScript.stevieg
Yes, I am reading more, and I need is the Implicit Grand Type aaronparecki.com/2012/07/29/2/oauth2-simplified but I dont know how to use it correctlySebastián A
IdentityServer4 has a public api type this is what I am trying to use.Steven T. Cramer
I recommend to read richard-banks.org/2018/11/… its a good start to see all thinks workingGunblades

1 Answers

3
votes

As far as I see, your code should work, it does everything.

  1. Your JavaScript-app (localhost:5003) requests a token (function signin()). This will result in redirecting to IdentityServer
  2. The IdentityServer (localhost:5000) is set up and the client settings (Client.cs) matches the request. Although configuration is missing for users and resources: see here: https://github.com/IdentityServer/IdentityServer4.Samples/blob/release/Quickstarts/3_ImplicitFlowAuthentication/src/QuickstartIdentityServer/Startup.cs
  3. Your JavaScript-app has a correct "landing page", a page where IdentityServer redirects back after successful login. This page picks up the newly issued token (new Oidc.OidcClient().processSigninResponse())
  4. Your JavaScript-app sends the token along its API-request (xhr.setRequestHeader("Authorization", "Bearer " + user.access_token);)
  5. Your API (localhost:5001) is set up correctly and will authorize against your IdentityServer

So I think the code is about right, but there're some misunderstandings regarding the terms.

  • You need Implicit grant. Forget about ClientCredentials, because it is designed for another workflow and should not be used in a browser, because the client secret is being exposed. This means that anyone could issue a valid token ("steal") and your security is compromised. (If you must use ClientCredentials, create a server proxy method).
  • You need both OpenID Connect (OIDC) and OAuth2. (These are not accurate definitions!!) OIDC issues the token for you ("logs the user in"), while OAuth2 validates the token. Don't worry, IdentityServer takes care all of that.
  • You need access token. Identity token is issued for the requestor (your localhost:5003 JavaScript application), Access token should be "sent forward" to an API (your localhost:5001 API)
  • The redirect for "login" is normal. This is typical workflow for web application: you leave your application (localhost:5003) and ends up in IdentityServer (http://localhost:5000). After successful login, you're being redirected back to your application (localhost:5003)