11

I am trying to implement the use of an API service which requires JWT authentication for all its API calls.

I understand what JWT tokens are and how they are used, my issue is that I am writing a Swift app and can't quite figure out the process to generate the token so that I can attach it as a Bearer in my API calls.

  • Can I generate the JWT token on the client side (swift app)?
  • Create a Google Cloud Function to generate token then write back to Firebase to use in my API calls?
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
Roggie
  • 1,157
  • 3
  • 16
  • 40
  • There's probably a misconception. An authentication provider may return a JWT to your app as part of the authentication. In OIDC for example, this is a signed JWT, more precisely, the "id_token" containing user info. Your app should "validate" this token, but an app usually does not create such JWTs. Likewise, if you receive an access_token from any token endpoint, this is always an opaque value. In the app you never try to parse or verify an access token, even when this access token is actually a JWT (or JWS) which you can inspect. – CouchDeveloper Feb 03 '21 at 15:36
  • I have to send encrypted username and password to server via URL by leaving the app and keeping the user signed in on browser? Any leads on this... – Mak13 Nov 03 '21 at 07:07

3 Answers3

21

Here's how to make JSON Web Tokens in Swift using Apple's CryptoKit. It uses the default example in https://jwt.io

import CryptoKit

extension Data {
    func urlSafeBase64EncodedString() -> String {
        return base64EncodedString()
            .replacingOccurrences(of: "+", with: "-")
            .replacingOccurrences(of: "/", with: "_")
            .replacingOccurrences(of: "=", with: "")
    }
}

struct Header: Encodable {
    let alg = "HS256"
    let typ = "JWT"
}

struct Payload: Encodable {
    let sub = "1234567890"
    let name = "John Doe"
    let iat = 1516239022
}

let secret = "your-256-bit-secret"
let privateKey = SymmetricKey(data: Data(secret.utf8))

let headerJSONData = try! JSONEncoder().encode(Header())
let headerBase64String = headerJSONData.urlSafeBase64EncodedString()

let payloadJSONData = try! JSONEncoder().encode(Payload())
let payloadBase64String = payloadJSONData.urlSafeBase64EncodedString()

let toSign = Data((headerBase64String + "." + payloadBase64String).utf8)

let signature = HMAC<SHA256>.authenticationCode(for: toSign, using: privateKey)
let signatureBase64String = Data(signature).urlSafeBase64EncodedString()

let token = [headerBase64String, payloadBase64String, signatureBase64String].joined(separator: ".")
print(token) // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Eric
  • 16,003
  • 15
  • 87
  • 139
  • This is a nice example how to create a signed JWT using Apple's new CryptoKit library. We need to be cautious, however: you are using a symmetric key which requires to be shared between the parties. Unless you have a fancy way to securely share secrets, the key probably ends up being in the binary of the app. A mobile app is considered "non-confidential", means anyone can get his hands on the key. And that means: it's not secure ;) – CouchDeveloper Feb 03 '21 at 15:44
  • @CouchDeveloper If you forego authenticating your network requests via a JWT, and if your network requests are confidential, you're actually increasing your attack surface. I'd obfuscate my binary and use JWTs. – Eric Feb 03 '21 at 18:12
  • Access tokens on the client are always opaque - that is, the app does not (need to) know what it is actually - so, access tokens as JWT are basically just a bunch of octets. Not so for any other info that has been sent by the server to the client: in OIDC for example, the "id_token" will be sent as a signed JWT. In this case, the client needs to parse and validate the JWT using a key. For mobile and SPA, you should use an algorithm which uses a public - private key mechanism (RSA, eclyptic curve, etc.). The public key can be safely used in non-confidential clients. – CouchDeveloper Feb 04 '21 at 21:30
  • So, when you receive data from the server, which is considered confidential (an access token for example) you SHALL store it into the key chain (which is very secure). You can trust the server, as long you are using HTTPS and proper TLS. So, what you get from the server should be the info you are after. You cannot have confidential data of any sort in the binary of your app, a symmetric key for example. It is only a matter of whether it's worth to crack the app, to get to the key - eventually. ;) – CouchDeveloper Feb 04 '21 at 21:39
  • If an attacker is capable of reading a secret key you've embedded in the binary, they'd equally be capable to crack your TLS and read the key your app receives from the backend. Once an attacker has access to your app binary, it's game over regardless of what security measures you have in place, whether it's JWT, obfuscation, TLS, etc.. Obviously this is not a reason to forego TLS or JWTs (quite the contrary). In practical terms, I think obfuscating the binary is a good compromise. So far there haven't been reports of binary obfuscation being broken. – Eric Feb 05 '21 at 16:20
  • How is the private key provided? Is it just the contents of the .p8 file? Witch the header and footer line? – inexcitus Jun 28 '21 at 16:48
  • @inexcitus the private key is just a long string. In the example above it's `"your-256-bit-secret"` but it should ideally be longer – Eric Jun 29 '21 at 08:36
  • @Eric if supporting earlier iOS versions and using CommonCrypto, then what out of that library should be used as a replacement for SymmetricKey()? Thanks – Gruntfluffle Jan 10 '22 at 15:05
  • "HMAC" now needs to have "CryptoKit." in front, otherwise there will be compile error "Cannot specialize a non-generic definition". Great reference btw thank you! – Daniel Sep 27 '22 at 06:20
  • how to reverse this code ? and decode the token with secret key ? – Kodr.F Dec 11 '22 at 12:22
4

It depends on how you plan to sign your token. Fundamentally, you'll want some sort of secret to sign the payload of the JWT.

Is your secret an API key that the client already has? If so, there's not a lot of harm just generating it client side.

Is your secret a certificate that's super secret and you can't give out to clients? Then you'll probably want to go with your Firebase idea.

It's pretty common to just have the client do the signing via API key in these situations, but your motivations for locking down your API to begin with are the driving force here.

IBM-Swift looks like the most complete JWT library for swift these days should you decide to go client side.

Jsonwebtoken is a very good JS one should you decide to deploy a GC Function.

Both libraries are very straightforward to use.

ZachChilders
  • 415
  • 3
  • 15
  • 1
    Yes the `secret` is something that can't be published so looks like I need to go down the GC Function route. Any GC Functions sample functions you may be able to point me to?, what triggers can be used for this to work? etc.. – Roggie Feb 19 '20 at 00:00
  • Try using an HTTP Function like described here: https://cloud.google.com/functions/docs/writing/http You'll want to inject your secret via environment variable here: https://cloud.google.com/functions/docs/env-var You can the examples in the Jsonwebtoken docs to sign a normal JSON object, which you'll return as your response to the HTTP request you send to the Cloud Function (or pass through FireBase): https://github.com/auth0/node-jsonwebtoken – ZachChilders Feb 19 '20 at 01:36
  • For most use cases, you don't need a third-party library to encode a JWT. Swift's CryptoKit provides all the functionality you need. – Eric Jan 19 '21 at 13:28
  • @Eric what if you have a requirement to support iOS versions earlier than iOS 13,then CryptoKit is not an option? – Gruntfluffle Jan 08 '22 at 15:33
  • @Gruntfluffle no, CryptoKit is only available on iOS 13 and above. On earlier iOS you can use CommonCrypto. See this: https://stackoverflow.com/a/52785143/1072846 – Eric Jan 09 '22 at 17:03
0

Thought it would be useful to add an example using Kitura SwiftJWT. This is based on creating a JWT for Apple’s App Store Connect API as described in WWDC18 Session 303 "Automating App Store Connect".

import Foundation
import SwiftJWT

let privateKeyId = "KI21S9DGKA"
let downloadsFolder = "/Users/johapp/Downloads"
let privateKeyPath = URL(fileURLWithPath: "\(downloadsFolder)/AuthKey_\(privateKeyId).p8")
let privateKey: Data = try Data(contentsOf: privateKeyPath, options: .alwaysMapped)
let header = Header(kid: privateKeyId)

struct MyClaims: Claims {
    let issuerId: String
    let audience: String
    let expiryTime: Date
}

let twentyMinutes = Date(timeIntervalSinceNow: 3600)
let claims = MyClaims(issuerId: "1623hdgak-8daf-98db-l1xa-d9aj2vxj2a",
                        audience: "appstoreconnect-v1",
                        expiryTime: twentyMinutes)

var jwt = JWT(header: header, claims: claims)
let jwtSigner = JWTSigner.es256(privateKey: privateKey)
let signedJwt = try jwt.sign(using: jwtSigner)

print(signedJwt)

To try out the above, copy paste the signedJwt output into Terminal:

curl https://api.appstoreconnect.apple.com/v1/apps --Header "Authorization: Bearer copyPasteSignedJwtOutputHere”

For those interested in the App Store Connect API, something one must pay particular attention to is the expiry time. For this I must thank the poster on Connecting to Apple Store Connect using Swift (with SwiftJWT) and REST API's - failing with 401

That is, exceeding Apple’s expiry time limit - of twenty minutes - will result in a misleading 401 NOT_AUTHORIZED error.

Max MacLeod
  • 26,115
  • 13
  • 104
  • 132